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)    收藏  举报

导航