thunkify
code
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 |
|
例子
1 2 3 4 5 6 7 8 |
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 |
|
1 2 3 4 5 6 7 8 |
|
TBC 继续挖坑待填…
源码简况
1 2 3 4 5 6 7 8 |
|
上面是一个最简单的服务,输出Hello World
; 我们用到两个koa的api
下面就先从app.listen() 开始
1 2 3 4 |
|
http.createServer([requestListener]) returns a new web server object
requestListener
is a function which is automatically added to the'request'
事件
由代码可以知道 this.callback() 就是一个 requestListener
;
来到app.callback
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 |
|
上面的代码基本上就解释了整个请求到回复的流程
附张图, 一图胜千言
下面分解开来
createContext(req, res)
finished(ctx, ctx.onerror)
fn.call(ctx, ctx.onerror)
Object.create(this.context)
request.app=response.app=
request.req = response.app = req
request.res = response.res = res
response.ctx = context
context.onerror.bind(context)
this.contextrequest.originalUrl = req.url
new Cookies(req, res, this.keys)
–> cookie TODOrequest.accept = accepts(app)
–> acceptsTBC
挖坑待续
在看源码前, 可以先看下Lazy.js的基本思想 Lazy.js 的设计模式)
1 2 3 4 5 |
|
sequence
sequence
对象提供对 0或者更多连续元素的集合 的统一的 API 封装.为什么所有的操作需要一个 sequence. 看下面的例子
1 2 3 4 5 |
|
上面这个例子中 前四步除了创建对应的sequece没有做任何的遍历source或者别的操作。 只有在第5步调用 each 时,将一次性按照鍊條(chain)的順序处理source 得到最后的结果。所以lazy做的就是延迟遍历处理数据.
in fact, when i think about the performance of
underscore
andlazy.js
; i cann’t understand why lazy is faster. lazy.js: 1 2s 3 underscore: 1 2 3 1 2 3 1 2 3 . so what’s the difference. lazy.js just hold off some process; i cann’t get it…. so continue to read code. >_<
1
|
|
TBC
运行上面的例子
reapIntervalMillis
检查空闲并移除, 默认1000msidleTimeoutMillis
关闭并新建连接, 问题是为什么不保持一个连接而要不停的关闭新建, 如果这样不如)acquired
, 然后调用其创建的 item 作为参数acquired
, 资源准备就续, 就等消费了curl http://127.0.0.1:8080
acquire(callback, priority)
removeIdle(): check and removes the available clients that have timed out. 见removeIdle
me.destroy(): client to be destroyed.见destroy
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 |
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 |
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 |
|
1 2 3 4 5 6 7 8 |
|
Future 代表一个函数的调用结果. 函数返回一个值或者抛出一个异常, 所以future 包含一个值或者一个异常(可以通过future.result()获得这个结果,异常和返回值). Fuures存在与对应的函数结束前. 在 一个 多线程场景下,简单的调用 future.result()等待另外一个线程或者进程完成。在 异步场景下,你可以附带一个callback给future为了当调用结束可以得到通知。(with future.add_done_callback or io_loop.add_future)
Futures
已经在Python3.2应用了 concurrent.futures , 如果在python3.2之前版本用 可以 (pip install futures). 那在 tornado 中如果可以将用就用python包,否则将会使用一个兼容的类 tornado.concurrent.Future
这个类封装了异步操作的结果。在同步程序中, Futures被用来等待一个线程或者进程池的结果。在Tornado一般用在 IOLoop.add_future 或者 在一个 gen.coroutine
1 2 3 4 5 6 7 |
|
1 2 3 4 5 6 7 8 |
|
这个函数先与result唯一的不同是 无异常的时候 return None ;
1 2 3 4 5 6 7 8 |
|
存储 异常的追踪
1 2 3 4 5 6 7 8 9 10 |
|
1 2 3 4 5 6 7 8 9 10 11 12 13 |
|
这里有关于run_on_executor
的应用例子 https://gist.github.com/zs1621/7921770
Future
看 tornado 源码的 test文件 concurrent_test.py;
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 |
|
联合 concurrent.py –> return_future
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 |
|
f(args, kwargs): future = sync_process()
从 a-b 可以理解 replacer.replace 的作用: 提取callback 的值, 并将callback 放入kwargs; 由c 可以知道 f() 函数是不会return 的; f()的结果只能由 future.result() 得到, 只要知道reuturn_future 是返回Future 本身是不会return 的, 如果return 就会报错; 由d 可知匿名函数
fuction <lambda> at 0xb6ba8f0c
赋值给了callback,而这个匿名函数的作用就是set_result
。
f(args, kwargs, callback): future = sync_process() callback(future)
与无回调相比; 明显多出 e log; run_callback 就是将 future.result()作为callback的参数运行;
f(args, kwargs): future = async_process()
与同步无回调相比; 看
test_async_future(self)
, 将 f() 获得的 future –> self.io_loop.add_future(future, self.stop) –> self.stop(future) –> 最后通过 self.wait()获得的结果 就是 例子中42 . 现实中一般将 return_future 与 gen.engine 联合使用 , 通过在 gen.engine –> yield f() 获得结果
对着IOLoop 的源码瞅了一天,楞是没有明白, 为什么 父类实例可以调用子类方法?
看一下可配置接口的实现
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 |
|
Configurable类是可配置接口的父类, 可配置接口对外提供一致的接口标志, 但它的子类实现可以在运行时进行configure。一般跨平台时由于子类实现有多种选择, 这时候就可以使用配置接口, 例如 select 和 epoll。首先注意 Configurable 的两个函数: configurable_base 和 configurable_default, 两函数都需要被子类(即可配置接口类)覆盖重写。其中, base函数一般返回接口类自身, default 返回接口的默认子类实现, 除非接口指定了 __impl_class。IOLoop及其子类实现都没有实现初始化函数也没有构造函数, 七构造函数继承于 Configurable, 如下::
1 2 3 4 5 6 7 8 9 10 11 12 13 |
|
当子类对象被构造时, 子类new被调用, 因此参数里的cls 指的是Configurable的子类(可配置接口类, 如IOLoop)。先得到base, IOLoop代码 可知, configurable_base返回的是自身类。由于 base 和 cls 是一样的, 所以调用 configured_class() 得到接口的子类实现(见configured_class) 其实就是调用 base的 configurable_default(?????TBD), 就是返回一个子类实现(epoll/kqueue/select之一),顺便把impl_kwargs合并到args 里 。然后调用Configurable类的父类(Object)的 new__方法, 生成一个impl的对象, 紧接着把args当参数调用该队想的initialize(继承PollIOLoop) , 返回该对象。 所以, 当构造IOLoop对象时, 实际得到的是EPollIOLoop或其它相关子类。可以看出, Configurable 类主要提供构造方法, 相当于对象工厂根据配置来生产对象, 同时开放configure接口以供配置。而子类按照约定调整配置即可得到不同对象, 代码得到了复用 或其它相关子类。可以看出, Configurable 类主要提供构造方法, 相当于对象工厂根据配置来生产对象, 同时开放configure接口以供配置。而子类按照约定调整配置即可得到不同对象, 代码得到了复用
上面的过程如果不好太理解 可以去看 example 这样大致能理解 ioloop 实例的初始化过程
1 2 3 4 |
|
===
上面主要解释了 IOLoop 为什么能调用子类方法 以及 可配置接口的实现 下面来看 IOLoop 的对象 instance
IOLoop 实现了单例的概念, 具体见 IOLoop单例
理解了上面的概念 接着 TCPServer 最后的 add_handle !其实 此时的 object 已经是确定的 EOLoop
或者 Kqueue
的对象! 这里的 add_handle 是 它们的父类 POLoop
的 方法, 这明显就是继承了!
add_handler 代码如下, 首先把 处理方法的上下文 存入 handlers ,等调用时再恢复, 这个机制是 statck_context 见 statck_content 做到的。 第二步 先来看下 self.impl 从哪里来 –> self._impl = impl 此时需要知道是谁调用 initialize –> 这里初始化是在构造函数 new 里调用的, instance.initialize(**args)
此时的instance 为 EPollIOLoop
实例 –> super(EPollIOLoop, self).initialize(impl=select.epoll, **kwargs)
–> EPollIOLoop 的父类的 initialize() 很明显 impl 为 select.epollepoll
1 2 3 |
|
这步呢 就是把 监听 fd 和 accept_handler方法进行关联, 至此事件分发到此就结束了
======
下面来看下 IOLoop 的主循环 start()
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 |
|
上面这段代码 TBD
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 |
|
一个非阻塞, 单进程的 TCP
服务器
为了使用 TCPServer
, 定义一个子类改写其中的 handle_stream
方法
如果让服务器通过 SSL
传输, 如下例
1 2 3 4 |
|
TCPServer
simple single-process::
listen
: 监听单个进程1 2 3 |
|
bind
/ start
: simple multi-process::1 2 3 4 |
|
如果使用start
接口, 一个 .IOLoop
不可以传入 TCPServer
结构里。 start
将总是开始服务在默认的单例 .IOLoop
add_sockets
: 高级多进程::1 2 3 4 5 |
|
add_sockets
接口更为复杂, 但是和tornado.process.fork_processes
使用将会更明了, add_sockets
也可以用在单进程服务中 , 如果你想要创建你的监听套节字而不是~tornado.netutil.bind_sockets
以上是怎么应用 下面看源码
初始化
1 2 3 4 5 6 7 8 |
|
listen
在给定的端口接受连接
这个方法可能被调用多次为了监听多个端口。listen
立即生效; 之后不必调用TCPServer.start
, 但是, ,IOLoop
是必要的
1 2 |
|
源码里的 bind_sockets
见 bind_sockets, add_sockets
见下
add_sockets
让服务接受多个连接
sockets
参数是一个socket数组, add_sockets
一般和 tornado.process.fork_processes
联合使用 为了控制 多进程服务的启动
1 2 3 4 5 6 |
|
上面的 add_accept_handler 见 add_accept_handler add_accept_handler 第二个参数 self._handle_connection是个回调函数, 分析如下
_handle_connection在接受客户端的连接处理结束后会被调用,调用时传入连接和ioloop对象初始化 IOStream,用于对客户端的异步读写;然后调用
handle_stream
(注意这里的handle_stream 文档说了如果你只是用tcpserver
那么,handle_stream得自己重写, 如果用tornado的httpserver 那handle_stream 在 httpserver), 传入创建的IOStream
对象初始化一个HTTPConnection
,HTTPConnection
封装了IOStream
的一些操作, 用于处俩HTTPRequest
并返回。 至此HTTPServer
的创建、启动、注册回调函数过程结束
1 2 3 4 5 6 7 8 9 |
|
从上面的分析和源码 可知 服务器的工作流程 socket->bind->listen创建 listen socket 监听客户端, 并将每个listen socket 的 fd 注册到IOLoop的单例实例中; 当 listen socket 可读时回调 _handle_events 处理客户端请求;在与客户端通信的过程中使用 IOStream 封装读写缓冲区, 实现与客户端的异步读写。 下面我们将具体了解listen socket 的 fd 被注册到IOLoop的单例实例中 见 IOLoop
bind_sockets – 解释:创建监听套节字绑定到给定的端口和地址
参数
IP
socket.AF_INET
或 socket.AF_INET6
socket.listen()<socket.socket.listen>
一样代码
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 |
|
add_accept_handler: 添加一个IOLoop
事件去接受新的连接在 sock
当一个连接被接受了,
callback(connection, address)
(connection
是socket对象,address
是连接的另外结尾处的地址)将会运行
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 |
|
send
入口
1
|
|
send
函数返回 SendStream
构造函数
1 2 3 |
|
来看SendStream
request
string
路径1 2 3 |
|
Stream.prototype
1
|
|
下面都是 SendStream
原型链的方法
path
, this._index, return thisstatus
–>触发error
directory
的监听者 那么触发directory
pathname
是否有潜在的问题,判断方法如果 没有_root
且 path
包含..
那么 就是有异常的路径;/
aa.html
(res.statusCode >= 200 && res.statusCode < 300) || 304 == res.statusCode
fresh
content
的头 key
主体方法 pipe
, 参数 res
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 |
|
send
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 |
|
stream
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 |
|
应用
1 2 3 4 5 6 |
|
对照应用例子理解源码
1 2 3 4 5 6 7 8 |
|
参数
x-real-ip
或x-forwarded-for
获取ip) False(当torando之前有反向代理或者负载均衡self.request.remote_ip只能获得127.0.0.1)ssl_options 使用例子
1 2 3 4 |
|
下面应该说下 TCPServer
主体内容在 TCPServer
,