首先你需要知道一个Web应用基本的请求处理流程。以最简单最原始的动态网页为例,你点击链接(GET),提交表单(POST),就是与服务器端建立了连接之后发送了一个
HTTP请求(RFC2616 5.1节,之后都以HTTP 1.1为例),里面至少有方法(动词,就是GET啦POST什么的,详见RFC2616第9节),地址(URL),HTTP版本,还可能带上Cookie(
会话的一般实现机制),缓存相关的信息(RFC2616 13节),User-Agent串等等一堆信息。对于POST请求我们还有表单内容作为
请求实体(RFC2616 7.2节),里面是你填写的表单内容。
于是我们有了一些关于请求的数据,不过现在一般来讲这些数据还在前端服务器( 反向代理,比如nginx,暂且忽略掉 负载均衡,反正是 透明的,也不考虑裸WSGI容器直接扛请求的情况)的手上,还没有传进后端语言(这里是Python)。我们就针对每一种语言都有特定的机制,用来将HTTP的请求信息映射到相应的编程语言范畴,叫做 Web服务器界面(Web server interface),通用如CGI/FCGI/SCGI,特定于某一语言如WSGI/PSGI/Rack/...,特定于某一操作系统如ISAPI(这货还活着?),一些已经不再使用的就不提了。总之在Python世界里这就是WSGI(PEP 3333, Web Server Gateway Interface),它就定义了Python语言与Web服务器之间的界面。在WSGI里,
于是我们有了一些关于请求的数据,不过现在一般来讲这些数据还在前端服务器( 反向代理,比如nginx,暂且忽略掉 负载均衡,反正是 透明的,也不考虑裸WSGI容器直接扛请求的情况)的手上,还没有传进后端语言(这里是Python)。我们就针对每一种语言都有特定的机制,用来将HTTP的请求信息映射到相应的编程语言范畴,叫做 Web服务器界面(Web server interface),通用如CGI/FCGI/SCGI,特定于某一语言如WSGI/PSGI/Rack/...,特定于某一操作系统如ISAPI(这货还活着?),一些已经不再使用的就不提了。总之在Python世界里这就是WSGI(PEP 3333, Web Server Gateway Interface),它就定义了Python语言与Web服务器之间的界面。在WSGI里,
- 请求的处理过程被映射为对应用callable的调用(application(environ, start_response),知乎不支持inline代码块?);
- 请求信息被映射到environ字典中的相应键值,比如请求方法被映射到environ['REQUEST_METHOD'],请求的“相对路径”被映射到environ['PATH_INFO'](过度简化;暂且不提WSGI应用挂载点,框架层一般也不用关心这个,挂载WSGI应用一般是WSGI容器如gunicorn、uWSGI之类组件的工作);
- 发送响应头的动作被映射到调用start_response(status, response_headers)(不考虑可选的第三个参数异常信息);
- 返回响应数据被映射到application返回iterable的动作。
于是响应便从Python返回到Web服务器,再被发送回浏览器,浏览器将响应内容渲染,一个请求就完成啦。
有了这样的感性认识,那么我们作为Python Web开发框架的作者,要做的事情就是在WSGI规范的基础之上,提供尽可能便捷的开发手段和尽可能低的框架开销,也即我们的代码将要工作在WSGI与业务逻辑的中间层。架构上,Web开发框架或多或少都遵循MVC的设计模式(Django管它叫MTV,其实差不多)。同时,由于框架位于中间件的位置,加上其鼓励模块化与代码复用的性质,自然需要为常见的HTTP操作提供抽象。这里就可以展开一些话题:
- 请求路径到view/controller的映射,请求参数的解析(routing,也叫路由)。
- 正则匹配的方案,比如Django内置了一个简单的正则表达式解析组件,能解析一般常见语法的正则表达式,把capturing groups解析成位置参数,named capturing groups解析成关键字参数。
- 也有DSL的方案,比如Werkzeug的路由组件。
- 请求实体的处理。表单解析,配合Web服务器进行上传文件处理。
- 正常的urlencoded表单,JSON表单,text/plain数据,multi-part表单
- multi-part附件,附件操作API
- 大文件上传(这个一般会被前端服务器保存在磁盘上的临时文件里,比方说nginx就是这么实现的)。
- 会话。HTTP是无状态(stateless)的,这个特点非常重要。如果没有会话,你连续做几个请求,却没有手段证明你们是同一个人/同一台机器(你完全可能是代理服务器)。
- 存储会话数据的会话后端(内存数据结构?文件?RDBMS?Redis?Memcache?)
- 安全机制(HMAC什么的,可以参考beaker的secure cookie实现)
- 请求处理流程中的会话中间件(从Cookie中提取会话,从query string中提取会话,从自定义头中提取会话,等等)
- View/Controller界面。发挥你的创造力,用上你的工程经验。
- Function-based or Class-based views? 参考:Django, Bottle, web.py, Tornado等一票框架的做法
- 框架的可选机制与服务如何暴露,
- 装饰器?(比如@login_required 这种额外要求)
- 回调?(能想到的只有Tornado和Twisted这种异步框架做事情的方式,还有整个JS生态系统都是回调(不考虑Promise什么的)的思路)
- 传入应用(业务逻辑)层的数据结构如何设计?(HttpRequest等价物,名字可能记不清了)
- 响应数据结构如何设计?(HttpResponse等价物,同上)
- Function-based or Class-based views? 参考:Django, Bottle, web.py, Tornado等一票框架的做法
- 数据库操作封装。Web应用基本都是数据为中心,这个组件非常有必要,也是撰写可复用代码必须的一环,毕竟光是框架抽象了,数据库操作还是裸SQL什么的,到时候生产环境一换(比如MySQL变pgsql)还不是傻眼。
- 关系型数据库。一站式解决方案参考:Django ORM、SQLAlchemy;轻量级解决方案参考各数据库Python绑定。
- 非关系数据库。各数据库Python绑定(pymongo, riak, redis-py之类),这个没什么可替代方案了,因为本来各种NoSQL库都是适应某一特殊需求设计的,没什么互相替换的必要,那意味着重新进行技术选型。