前言
在 Python 众多的 HTTP 客户端中,最有名的莫过于requests
、aiohttp
和httpx
。
在不借助其他第三方库的情况下,requests
只能发送同步请求;aiohttp
只能发送异步请求;httpx
既能发送同步请求,又能发送异步请求。
那么怎么选择呢
- 只发同步请求用
requests
,但可配合多线程变异步。 - 只发异步请求用
aiohttp
,但可以配合await变同步。 httpx
可以发同步请求也可以异步,但是请求速度同步略差于requests
,异步略差于aiohttp
这里不建议使用多线程来做异步请求,建议使用异步IO的方式。
asyncio
的优势:
- 可以异步请求。
- 可以普通请求也可以作为WS客户端连接。
- 可以作为WEB服务器和WEBSOCKET服务器。
- 性能较好。
安装依赖
代码语言:javascript复制pip install aiohttp
客户端
默认超时时间
代码语言:javascript复制aiohttp.ClientTimeout(
total=5*60,
connect=None,
sock_connect=None,
sock_read=None
)
GET请求
基本请求
代码语言:javascript复制import aiohttp
import asyncio
async def main():
async with aiohttp.ClientSession() as session:
params = {'key1': 'value1', 'key2': 'value2'}
resp = await session.get(
'https://www.psvmc.cn/login.json',
params=params
)
result = await resp.text()
result2 = await resp.json()
print(result)
print(result2)
loop = asyncio.get_event_loop()
loop.run_until_complete(main())
获取状态码
代码语言:javascript复制import aiohttp
import asyncio
async def main():
async with aiohttp.ClientSession() as session:
params = {'key1': 'value1', 'key2': 'value2'}
async with session.get(
'https://www.psvmc.cn/login.json',
params=params
)as resp:
print(resp.status)
print(await resp.text())
loop = asyncio.get_event_loop()
loop.run_until_complete(main())
文件下载
代码语言:javascript复制import aiohttp
import asyncio
async def main():
async with aiohttp.ClientSession() as session:
params = {'key1': 'value1', 'key2': 'value2'}
async with session.get(
'https://www.psvmc.cn/search.json',
params=params
)as resp:
filename = "D://search.json"
chunk_size = 1000
with open(filename, 'wb') as fd:
async for chunk in resp.content.iter_chunked(chunk_size):
fd.write(chunk)
loop = asyncio.get_event_loop()
loop.run_until_complete(main())
POST请求
基本用法
代码语言:javascript复制import aiohttp
import asyncio
async def main():
async with aiohttp.ClientSession() as session:
resp = await session.post(
'https://www.psvmc.cn/login.json',
json={'keyword': '123'}
)
result = await resp.text()
print(result)
loop = asyncio.get_event_loop()
loop.run_until_complete(main())
Form-encoded
代码语言:javascript复制import aiohttp
import asyncio
async def main():
async with aiohttp.ClientSession() as session:
resp = await session.post(
'https://www.psvmc.cn/login.json',
data={'keyword': '123'}
)
result = await resp.text()
print(result)
loop = asyncio.get_event_loop()
loop.run_until_complete(main())
Multipart-Encoded File
To upload Multipart-encoded files:
代码语言:javascript复制url = 'http://httpbin.org/post'
files = {'file': open('report.xls', 'rb')}
await session.post(url, data=files)
You can set the filename
and content_type
explicitly:
url = 'http://httpbin.org/post'
data = FormData()
data.add_field('file',
open('report.xls', 'rb'),
filename='report.xls',
content_type='application/vnd.ms-excel')
await session.post(url, data=data)
Streaming uploads
代码语言:javascript复制with open("D://search.json", 'rb') as f:
await session.post('http://httpbin.org/post', data=f)
其它请求
代码语言:javascript复制session.put('http://httpbin.org/put', data=b'data')
session.delete('http://httpbin.org/delete')
session.head('http://httpbin.org/get')
session.options('http://httpbin.org/get')
session.patch('http://httpbin.org/patch', data=b'data')
WebSocket
代码语言:javascript复制async with session.ws_connect('http://example.org/ws') as ws:
async for msg in ws:
if msg.type == aiohttp.WSMsgType.TEXT:
if msg.data == 'close cmd':
await ws.close()
break
else:
await ws.send_str(msg.data '/answer')
elif msg.type == aiohttp.WSMsgType.ERROR:
break
发送消息
代码语言:javascript复制await ws.send_str('data')
服务端
WEB服务器
文本
代码语言:javascript复制from aiohttp import web
async def hello(request):
print(request.url)
return web.Response(text="Hello, world")
app = web.Application()
app.add_routes([web.get('/', hello)])
web.run_app(app, port=8080)
注解
代码语言:javascript复制from aiohttp import web
routes = web.RouteTableDef()
@routes.get('/')
async def hello(request):
print(request.url)
return web.Response(text="Hello, world")
app = web.Application()
app.add_routes(routes)
web.run_app(app, port=8080)
JSON
代码语言:javascript复制from aiohttp import web
routes = web.RouteTableDef()
@routes.get('/')
async def hello(request):
print(request.url)
data = {'some': 'data'}
return web.json_response(data)
app = web.Application()
app.add_routes(routes)
web.run_app(app, port=8080)
From参数获取
代码语言:javascript复制async def do_login(request):
data = await request.post()
username = data['username']
password = data['password']
文件上传服务
First, make sure that the HTML <form>
element has its enctype attribute set to enctype="multipart/form-data"
.
<form action="/store/mp3" method="post" accept-charset="utf-8"
enctype="multipart/form-data">
<label for="mp3">Mp3</label>
<input id="mp3" name="mp3" type="file" value=""/>
<input type="submit" value="submit"/>
</form>
Then, in the request handler you can access the file input field as a FileField
instance.
FileField
is simply a container for the file as well as some of its metadata:
async def store_mp3_handler(request):
# WARNING: don't do that if you plan to receive large files!
data = await request.post()
mp3 = data['mp3']
# .filename contains the name of the file in string format.
filename = mp3.filename
# .file contains the actual file data that needs to be stored somewhere.
mp3_file = data['mp3'].file
content = mp3_file.read()
return web.Response(
body=content,
headers={'CONTENT-DISPOSITION': mp3_file}
)
WebSockets
服务端
代码语言:javascript复制import aiohttp
from aiohttp import web
routes = web.RouteTableDef()
@routes.get('/ws')
async def websocket_handler(request):
ws = web.WebSocketResponse()
await ws.prepare(request)
async for msg in ws:
if msg.type == aiohttp.WSMsgType.TEXT:
print(f"receive:{msg.data}")
if msg.data == 'close':
await ws.close()
else:
await ws.send_str("响应:" msg.data)
elif msg.type == aiohttp.WSMsgType.BINARY:
print("receive:BINARY")
elif msg.type == aiohttp.WSMsgType.ERROR:
print('ws connection closed with exception %s' %
ws.exception())
print('websocket connection closed')
return ws
app = web.Application()
app.add_routes(routes)
web.run_app(app, port=8888)
客户端测试页面
页面
代码语言:javascript复制<!DOCTYPE html>
<html>
<head>
<meta name="viewport" content="width=device-width, initial-scale=1" />
<title>聊天客户端</title>
</head>
<body>
<div>
<div id="content"></div>
<input type="text" style="width: 100%" id="msg" />
<button type="button" onclick="emit()">发送</button>
</div>
<script
type="text/javascript"
src="http://apps.bdimg.com/libs/jquery/2.1.4/jquery.min.js"
></script>
<script type="text/javascript" src="js/index.js"></script>
</body>
</html>
JS
代码语言:javascript复制var socket = new WebSocket("ws://127.0.0.1:8888/ws");
$(function () {
listen();
})
function encodeScript(data) {
if (null == data || "" == data) {
return "";
}
return data.replace("<", "<").replace(">", ">");
}
function emit() {
var text = encodeScript($("#msg").val());
text = replace_em(text);
socket.send(text);
$("#content").append("<kbd style='color: #" "CECECE" "; font-size: " 12 ";'>" text "</kbd><br/>");
$("#msg").val("");
}
//替换为HTML上的标签
function replace_em(str) {
str = str.replace(/</g, '<');
str = str.replace(/>/g, '>');
str = str.replace(/n/g, '<br/>');
str = str.replace(/[em_([0-9]*)]/g, '<img src="arclist/$1.gif" border="0" />');
return str;
};
function listen() {
socket.onopen = function () {
$("#content").append("<kbd>连接成功! 时间(s):" parseInt(new Date().getTime() / 1000) "</kbd></br>");
heartCheck();
};
socket.onmessage = function (evt) {
$("#content").append(evt.data "</br>");
};
socket.onclose = function (evt) {
$("#content").append("<kbd>" "连接关闭! 时间(s):" parseInt(new Date().getTime() / 1000) "</kbd></br>");
}
socket.onerror = function (evt) {
$("#content").append("<kbd>" "ERROR!" "</kbd></br>");
}
}
//心跳包
function heartCheck() {
setInterval(function () {
if (socket) {
let buffer = new ArrayBuffer(2); // 初始化14个Byte的二进制数据缓冲区
let dataView = new DataView(buffer);
dataView.setInt16(0, 1);
socket.send(dataView);
console.info("发送心跳", " 时间(s):" parseInt(new Date().getTime() / 1000));
}
}, 30000);
}
document.onkeydown = function (event) {
var e = event || window.event || arguments.callee.caller.arguments[0];
if (e && e.keyCode == 13) { // enter 键
emit();
}
};