python 3.x asyncio模拟http请求
asyncio模拟http请求
- asyncio没有提供http协议的接口.提供了更底层的接口TCP,UDP协议接口.
- 想使用http协议去请求url的话,使用aiohttp,aiohttp是专门用作http服务的,首先aiohttp是可以搭建一个http服务器.aiohttp也可以做爬虫使用,就是把aiohttp当作异步的requests使用.
- 使用原身的asyncio来完成aiohttp,aio是基于asyncio来完成的.
asyncio模拟http请求
1 import asyncio 2 from urllib.parse import urlparse 3 import time 4 5 6 # 通过协程的方式,也就是以同步代码的形式,来编写高效的http请求. 7 async def get_url(url): 8 new_url = urlparse(url) 9 host = new_url.netloc 10 path = new_url.path 11 if path == "": 12 path = "/" 13 14 # client = socket.socket(socket.AF_INET, socket.SOCK_STREAM) 15 # client.setblocking(False) 16 # client.connect((host, 80)) # 阻塞不会消耗cpu 17 # client.send("GET {} HTTP/1.1\r\nHost:{}\r\nConnection:close\r\n\r\n".format(path, host).encode("utf8")) 18 19 # 等待连接建立好的,希望await可以返回一个可以直接发送的socket. 20 # asyncio提供了一个方法open_connection(),open_connection是一个协程. 21 # open_connection()可以跟服务端建立一个连接. 22 # 如果使用client.setblocking(False),需要使用selector.register(self.client.fileno(), EVENT_READ, self.readable),所以说这一步是省略不了的 23 # 这里就出现疑问了open_connection()是怎样完成register注册的呢??? 24 # open_connection()方法,返回reader, writer 25 # 建立socket连接是耗时的操作,所以需要await,跳转到另外的一个协程. 26 # open_connection是一个协程.open_connection()执行完后会自动调用这个地方.会直接驱动get_url协程 27 reader, writer = await asyncio.open_connection(host, port=80) 28 # 直接写数据,write()方法内部的实现还是调用send()方法 29 # write()方法实际上帮助我们完成unregister以及之前说过send()之后要重新register一个可读的事件,write()方法的内部已经帮我们实现重新Register的逻辑 30 writer.write("GET {} HTTP/1.1\r\nHost:{}\r\nConnection:close\r\n\r\n".format(path, host).encode("utf8")) 31 32 all_lines = [] 33 # async for语法 34 # 在对某一阻塞的对象进行for循环,将读取数据异步化 35 # async 背后实现了魔法函数__anext__ 36 async for raw_line in reader: 37 data = raw_line.decode("utf8") 38 all_lines.append(data) 39 html = "\n".join(all_lines) 40 print(html) 41 return html 42 43 44 if __name__ == '__main__': 45 start_time = time.time() 46 loop = asyncio.get_event_loop() 47 tasks = [] 48 for i in range(1, 21): 49 url = "http://shop.projectsedu.com/goods/{}/".format(i) 50 tasks.append(get_url(url)) 51 loop.run_until_complete(asyncio.gather(*tasks)) 52 print("code run finished use time: {}".format(time.time() - start_time)) 53 54 """ 55 输出结果: 56 HTTP/1.1 200 OK 57 Server: nginx/1.16.1 58 Date: Fri, 27 Dec 2019 05:06:14 GMT 59 Content-Type: application/json 60 Content-Length: 1639 61 Connection: close 62 Vary: Accept, Cookie 63 Allow: GET, HEAD, OPTIONS 64 X-Frame-Options: SAMEORIGIN 65 66 {"id":13,"category":{"id":121,"sub_cat":[{"id":122,"sub_cat":[],"name":"松花蛋/咸鸭蛋","code":"xhd_xyd","desc":"", 67 "category_type":3,"is_tab":false,"add_time":"2017-07-29T18:56:34","parent_category":121},{"id":123,"sub_cat":[], 68 "name":"鸡蛋","code":"jd","desc":"","category_type":3,"is_tab":false,"add_time":"2017-07-29T18:56:34", 69 "parent_category":121}],"name":"蛋制品","code":"dzp","desc":"","category_type":2,"is_tab":false, 70 "add_time":"2017-07-29T18:56:34","parent_category":110},"images":[{ 71 "image":"http://shop.projectsedu.com/media/goods/images/5_P_1448945270390.jpg"},{ 72 "image":"http://shop.projectsedu.com/media/goods/images/5_P_1448945270067.jpg"},{ 73 "image":"http://shop.projectsedu.com/media/goods/images/5_P_1448945270432.jpg"}],"goods_sn":"", 74 "name":"澳洲进口安格斯牛切片上脑牛排1000g","click_num":1650,"sold_num":0,"fav_num":0,"goods_num":-1,"market_price":144.0, 75 "shop_price":120.0,"goods_brief":"澳大利亚是国际公认的没有疯牛病和口蹄疫的国家。为了保持澳大利亚产品的高标准, 76 澳大利亚牛肉业和各级政府共同努力简历了严格的标准和体系,以保证生产的整体化和产品的可追溯性", 77 "goods_desc":"<p><img src=\"/media/goods/images/2_20170719161405_249.jpg\" title=\"\" alt=\"2.jpg\"/></p> 78 <p><img src=\"/media/goods/images/2_20170719161414_628.jpg\" title=\"\" alt=\"2.jpg\"/></p> 79 <p><img src=\"/media/goods/images/2_20170719161435_381.jpg\" title=\"\" alt=\"2.jpg\"/></p>","ship_free":true, 80 "goods_front_image":"http://shop.projectsedu.com/media/goods/images/5_P_1448945270390.jpg","is_new":false, 81 "is_hot":false,"add_time":"2017-07-31T23:53:54"} 82 83 ...省略其他的结果... 84 85 code run finished use time: 2.284130573272705 86 """
asyncio模拟http请求(去除注释的代码)
1 """ 2 asyncio模拟http请求. 3 """ 4 5 import asyncio 6 from urllib.parse import urlparse 7 import time 8 9 10 async def get_url(url): 11 new_url = urlparse(url) 12 host = new_url.netloc 13 path = new_url.path 14 if path == "": 15 path = "/" 16 reader, writer = await asyncio.open_connection(host, port=80) 17 writer.write("GET {} HTTP/1.1\r\nHost:{}\r\nConnection:close\r\n\r\n".format(path, host).encode("utf8")) 18 19 all_lines = [] 20 async for raw_line in reader: 21 data = raw_line.decode("utf8") 22 all_lines.append(data) 23 html = "\n".join(all_lines) 24 # 提问: 怎样才能不在协程get_url中打印html的结果,而是在外部直接获取html的结果. 25 print(html) 26 return html 27 28 29 if __name__ == '__main__': 30 start_time = time.time() 31 loop = asyncio.get_event_loop() 32 tasks = [] 33 for i in range(1, 21): 34 url = "http://shop.projectsedu.com/goods/{}/".format(i) 35 tasks.append(get_url(url)) 36 loop.run_until_complete(asyncio.gather(*tasks)) 37 print("code run finished use time: {}".format(time.time() - start_time)) 38 39 """ 40 输出结果: 41 code run finished use time: 2.324403762817383 42 """
open_connection()是怎样完成register注册的呢???
源码open_connection():
1 @coroutine 2 def open_connection(host=None, port=None, *,loop=None, limit=_DEFAULT_LIMIT, **kwds): 3 if loop is None: 4 loop = events.get_event_loop() 5 reader = StreamReader(limit=limit, loop=loop) 6 protocol = StreamReaderProtocol(reader, loop=loop) 7 8 # 此处查看create_connection()源码 9 transport, _ = yield from loop.create_connection(lambda: protocol, host, port, **kwds) 10 writer = StreamWriter(transport, protocol, reader, loop) 11 return reader, writer
源码StreamReader:
1 class StreamReader: 2 # 省略... 3 4 @coroutine 5 def readexactly(self, n): 6 # 省略... 7 if compat.PY35: 8 @coroutine 9 def __aiter__(self): 10 return self 11 12 # 迭代协议__anext___(),并且是一个协程. 13 @coroutine 14 def __anext__(self): 15 # readline() 16 val = yield from self.readline() 17 if val == b'': 18 raise StopAsyncIteration 19 return val
源码readline():
1 @coroutine 2 def readline(self): 3 sep = b'\n' 4 seplen = len(sep) 5 try: 6 # 直接调用readuntil()方法 7 line = yield from self.readuntil(sep) 8 except IncompleteReadError as e: 9 return e.partial 10 except LimitOverrunError as e: 11 if self._buffer.startswith(sep, e.consumed): 12 del self._buffer[:e.consumed + seplen] 13 else: 14 self._buffer.clear() 15 self._maybe_resume_transport() 16 raise ValueError(e.args[0]) 17 return line
源码readuntil():
1 @coroutine 2 def readuntil(self, separator=b'\n'): 3 # 省略... 4 5 while True: 6 # 省略... 7 yield from self._wait_for_data('readuntil') 8 9 # 省略...
源码_wait_for_data():
1 @coroutine 2 def _wait_for_data(self, func_name): 3 # 省略... 4 5 if self._paused: 6 self._paused = False 7 # 继续读取数据 8 self._transport.resume_reading() 9 10 self._waiter = self._loop.create_future() 11 try: 12 yield from self._waiter 13 finally: 14 self._waiter = None
源码resume_reading():
1 def resume_reading(self): 2 if not self._paused: 3 raise RuntimeError('Not paused') 4 self._paused = False 5 if self._closing: 6 return 7 # 向loop里面添加_add_reader 8 self._loop._add_reader(self._sock_fd, self._read_ready) 9 if self._loop.get_debug(): 10 logger.debug("%r resumes reading", self)
源码_add_reader():
1 def _add_reader(self, fd, callback, *args): 2 self._check_closed() 3 # 当有数据的时候会调用callback回调函数,callback就是resume_reading()源码中的_read_ready 4 handle = events.Handle(callback, args, self) 5 try: 6 key = self._selector.get_key(fd) 7 except KeyError: 8 # 注册一个读的事件 9 self._selector.register(fd, selectors.EVENT_READ, 10 (handle, None)) 11 else: 12 mask, (reader, writer) = key.events, key.data 13 self._selector.modify(fd, mask | selectors.EVENT_READ, 14 (handle, writer)) 15 if reader is not None: 16 reader.cancel()
源码_read_ready():
1 def _read_ready(self): 2 if self._conn_lost: 3 return 4 try: 5 # 调用socket的recv的方法,这就是之前自己介绍的逻辑 6 data = self._sock.recv(self.max_size) 7 except (BlockingIOError, InterruptedError): 8 pass 9 except Exception as exc: 10 self._fatal_error(exc, 'Fatal read error on socket transport') 11 else: 12 if data: 13 self._protocol.data_received(data) 14 else: 15 if self._loop.get_debug(): 16 logger.debug("%r received EOF", self) 17 keep_open = self._protocol.eof_received() 18 if keep_open: 19 self._loop._remove_reader(self._sock_fd) 20 else: 21 self.close()
源码create_connection():
1 @coroutine 2 def create_connection(self, protocol_factory, host=None, port=None, *, 3 ssl=None, family=0, proto=0, flags=0, sock=None, 4 local_addr=None, server_hostname=None): 5 6 # 参数检查代码省略... 7 8 if host is not None or port is not None: 9 if sock is not None: 10 raise ValueError( 11 'host/port and sock can not be specified at the same time') 12 13 # 查看_ensure_resolved()源码,_ensure_resolved()方法执行完毕,立马返回一个future对象,并没有对IO进行操作 14 f1 = _ensure_resolved((host, port), family=family, type=socket.SOCK_STREAM, proto=proto,flags=flags, loop=self) 15 16 # 返回的future对象f1立马添加到fs列表中 17 fs = [f1] 18 if local_addr is not None: 19 f2 = _ensure_resolved(local_addr, family=family,type=socket.SOCK_STREAM, proto=proto,flags=flags, loop=self) 20 fs.append(f2) 21 else: 22 f2 = None 23 24 # wait()会等待future的完成,此处会阻塞,因为第一步会根据传递进来的host,port进行DNS解析.会等到future完成之后,在向下进一步执行. 25 # 通过url完成地址的解析.实际上是需要时间的,所以使用了yield from 26 yield from tasks.wait(fs, loop=self) 27 28 # 解析完后会获取到解析的结果 29 infos = f1.result() 30 31 # 省略... 32 for family, type, proto, cname, address in infos: 33 try: 34 # 获取解析结果之后,此处才是主体逻辑. 35 # 建立socket 36 sock = socket.socket(family=family, type=type, proto=proto) 37 # 设置成非阻塞模式. 38 sock.setblocking(False) 39 if f2 is not None: 40 for _, _, _, _, laddr in laddr_infos: 41 try: 42 # 绑定地址 43 sock.bind(laddr) 44 break 45 # 省略... 46 else: 47 # 省略... 48 else: 49 # 省略... 50 return transport, protocol
源码_ensure_resolved():
1 def _ensure_resolved(address, *, family=0, type=socket.SOCK_STREAM, proto=0, flags=0, loop): 2 host, port = address[:2] 3 # 对传递进来的url进行解析. 4 info = _ipaddr_info(host, port, family, type, proto) 5 if info is not None: 6 # 创建一个future对象,为了不使异步IO阻塞,所以在调用的时候,立马返回一个future 7 # 当future里面的任务执行完后,会直接发送一个结果给future 8 fut = loop.create_future() 9 fut.set_result([info]) 10 return fut 11 else: 12 return loop.getaddrinfo(host, port, family=family, type=type, proto=proto, flags=flags)
源码getaddrinfo():
1 def getaddrinfo(self, host, port, *, family=0, type=0, proto=0, flags=0): 2 if self._debug: 3 return self.run_in_executor(None, self._getaddrinfo_debug, 4 host, port, family, type, proto, flags) 5 else: 6 return self.run_in_executor(None, socket.getaddrinfo, 7 host, port, family, type, proto, flags)
源码socket.getaddrinfo()
-
getaddrinfo()是一个阻塞的方法,所以将getaddrinfo()放到线程池来运行.运行完后将结果放到future的result中.
1 def getaddrinfo(host, port, family=0, type=0, proto=0, flags=0): 2 addrlist = [] 3 for res in _socket.getaddrinfo(host, port, family, type, proto, flags): 4 af, socktype, proto, canonname, sa = res 5 addrlist.append((_intenum_converter(af, AddressFamily), 6 _intenum_converter(socktype, SocketKind), 7 proto, canonname, sa)) 8 return addrlist
源码sock_connect():selector_events.py模块下
1 @coroutine 2 def sock_connect(self, sock, address): 3 if self._debug and sock.gettimeout() != 0: 4 raise ValueError("the socket must be non-blocking") 5 6 if not hasattr(socket, 'AF_UNIX') or sock.family != socket.AF_UNIX: 7 resolved = base_events._ensure_resolved( 8 address, family=sock.family, proto=sock.proto, loop=self) 9 if not resolved.done(): 10 yield from resolved 11 _, _, _, _, address = resolved.result()[0] 12 13 fut = self.create_future() 14 self._sock_connect(fut, sock, address) 15 # 直接返回create_future(fut = self.create_future()) 16 # 在asyncio里面,凡是异步的地方都会创建一个future, 17 # 在使用同步IO库的时候都没有fut = self.create_future()这行语句. 18 # 凡是去完成异步IO的库主要是阻塞的地方,都需要自己去创建一个future 19 return (yield from fut)
源码_sock_connect():
1 def _sock_connect(self, fut, sock, address): 2 fd = sock.fileno() 3 try: 4 # 建立socket连接 5 sock.connect(address) 6 except (BlockingIOError, InterruptedError): 7 # 如果抛出BlockingIOError错误.会添加一个_sock_connect_done()方法 8 fut.add_done_callback(functools.partial(self._sock_connect_done, fd)) 9 self.add_writer(fd, self._sock_connect_cb, fut, sock, address) 10 except Exception as exc: 11 fut.set_exception(exc) 12 else: 13 fut.set_result(None)
源码_sock_connect_done():
1 def _sock_connect_done(self, fd, fut): 2 # 与add_write()关联,主要是用来注册与注销connect 3 self.remove_writer(fd)
源码add_writer():
1 def add_writer(self, fd, callback, *args): 2 self._ensure_fd_no_transport(fd) 3 return self._add_writer(fd, callback, *args)
源码_add_writer():
1 def _add_writer(self, fd, callback, *args): 2 self._check_closed() 3 handle = events.Handle(callback, args, self) 4 try: 5 key = self._selector.get_key(fd) 6 except KeyError: 7 # 在此处完成了selector的注册. 8 self._selector.register(fd, selectors.EVENT_WRITE, 9 (None, handle)) 10 else: 11 mask, (reader, writer) = key.events, key.data 12 self._selector.modify(fd, mask | selectors.EVENT_WRITE, 13 (reader, handle)) 14 if writer is not None: 15 writer.cancel()
源码remove_writer():
1 def remove_writer(self, fd): 2 self._ensure_fd_no_transport(fd) 3 return self._remove_writer(fd)
源码_remove_writer():
1 def _remove_writer(self, fd): 2 if self.is_closed(): 3 return False 4 try: 5 key = self._selector.get_key(fd) 6 except KeyError: 7 return False 8 else: 9 mask, (reader, writer) = key.events, key.data 10 # Remove both writer and connector. 11 mask &= ~selectors.EVENT_WRITE 12 if not mask: 13 # 此处注销selector. 14 self._selector.unregister(fd) 15 else: 16 # 变更selector 17 self._selector.modify(fd, mask, (reader, None)) 18 19 if writer is not None: 20 writer.cancel() 21 return True 22 else: 23 return False
怎样才能不在协程get_url中打印html的结果,而是在外部直接获取html的结果.
通过future对象的result()方法可以在外部直接获取协程get_url的返回结果.
1 import asyncio 2 from urllib.parse import urlparse 3 import time 4 5 6 async def get_url(url): 7 new_url = urlparse(url) 8 host = new_url.netloc 9 path = new_url.path 10 if path == "": 11 path = "/" 12 reader, writer = await asyncio.open_connection(host, port=80) 13 writer.write("GET {} HTTP/1.1\r\nHost:{}\r\nConnection:close\r\n\r\n".format(path, host).encode("utf8")) 14 15 all_lines = [] 16 async for raw_line in reader: 17 data = raw_line.decode("utf8") 18 all_lines.append(data) 19 html = "\n".join(all_lines) 20 # 提问: 怎样才能不在协程get_url中打印html的结果,而是在外部直接获取html的结果. 21 # 通过future对象的result()方法可以在外部直接获取协程get_url的返回结果. 22 # print(html) 23 return html 24 25 26 if __name__ == '__main__': 27 start_time = time.time() 28 loop = asyncio.get_event_loop() 29 tasks = [] 30 for i in range(1, 21): 31 url = "http://shop.projectsedu.com/goods/{}/".format(i) 32 # tasks.append(get_url(url))语句:直接将协程get_url放到tasks列表中 33 # 将协程get_url转变成future对象后,将future对象放到tasks列表中 34 tasks.append(asyncio.ensure_future(get_url(url))) 35 # gather()方法接收的参数既可以是协程对象也可以是future对象. 36 loop.run_until_complete(asyncio.gather(*tasks)) 37 # 通过future对象的result()方法可以在外部直接获取协程get_url的返回结果 38 for future_task in tasks: 39 print(future_task.result()) 40 41 print("code run finished use time: {}".format(time.time() - start_time)) 42 43 """ 44 输出结果: 45 code run finished use time: 2.324403762817383 46 """
怎样做到像as_complete()这个函数里面做到的执行完一个打印一个结果,执行完一个打印一个结果.
- asyncio也提供了一个函数as_completed()与线程池as_complete()方法的效果是一样的,某一个协程一但完成就会立马返回.
- as_completed()方法,返回值为协程的迭代器
- 源码as_completed():
-
1 def as_completed(fs, *, loop=None, timeout=None): 2 # Return an iterator whose values are coroutines.返回值为协程的迭代器 3 # ...省略代码 4 5 # _wait_for_one()是一个协程 6 @coroutine 7 def _wait_for_one(): 8 f = yield from done.get() 9 if f is None: 10 # Dummy value from _on_timeout(). 11 raise futures.TimeoutError 12 return f.result() # May raise f.exception(). 13 14 # ...省略代码 15 for _ in range(len(todo)): 16 # 返回协程对象. 17 yield _wait_for_one()
1 import asyncio 2 from urllib.parse import urlparse 3 import time 4 5 6 async def get_url(url): 7 new_url = urlparse(url) 8 host = new_url.netloc 9 path = new_url.path 10 if path == "": 11 path = "/" 12 reader, writer = await asyncio.open_connection(host, port=80) 13 writer.write("GET {} HTTP/1.1\r\nHost:{}\r\nConnection:close\r\n\r\n".format(path, host).encode("utf8")) 14 15 all_lines = [] 16 async for raw_line in reader: 17 data = raw_line.decode("utf8") 18 all_lines.append(data) 19 html = "\n".join(all_lines) 20 return html 21 22 23 async def main(loop): 24 tasks = [] 25 for i in range(1, 21): 26 url = "http://shop.projectsedu.com/goods/{}/".format(i) 27 tasks.append(asyncio.ensure_future(get_url(url))) 28 # as_completed()方法与线程池as_complete()方法的效果是一样的,某一个协程一但完成就会立马返回. 29 # as_completed()方法,返回值为协程的迭代器 30 for task in asyncio.as_completed(tasks): 31 # task就是一个协程对象,既然是协程就要使用await 32 result = await task 33 print(result) 34 35 36 if __name__ == '__main__': 37 start_time = time.time() 38 loop = asyncio.get_event_loop() 39 # 此处对协程main进行一个等待,等待协程main执行完成 40 loop.run_until_complete(main(loop)) 41 print("code run finished use time: {}".format(time.time() - start_time)) 42 43 """ 44 输出结果: 45 code run finished use time: 2.291131019592285 46 47 总结: 48 通过源码的大概解读,可以看到协程的内部原理都是实现事件循环以及socket进行注册和注销这些核心原理. 49 但是协程关键的地方做了future,凡是await的地方协程里面都返回了future对象,这个future对象是不用等待的. 50 future对于协程来说是非常的重要.协程不像传统的阻塞IO只有执行成功之后才会返回一个结果. 51 但是现在是一个协程,协程需要尽快的返回一个future. 52 所以说传统的阻塞驱动实际上已经在协程里面,如果要使用协程的并发特性的话,阻塞驱动是用不上的, 53 需要将传统阻塞IO里面的一些函数里面的返回,给我们返回成future(也就是将返回值包装成future). 54 """
********
posted on 2019-05-24 17:42 jaydenjune 阅读(61) 评论(0) 收藏 举报
浙公网安备 33010602011771号