Web框架的本质
web应用
众所周知,对于所有的web应用,本质上其实就是一个socket服务端,用户的浏览器其实就是一个socket客户端
import socket def handle_request(client): buf = client.recv(1024) client.send("HTTP/1.1 200 OK ") client.send("Hello, Seven") def main(): sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM) sock.bind(('localhost',8000)) sock.listen(5) while True: connection, address = sock.accept() handle_request(connection) connection.close() if __name__ == '__main__': main()
上述通过socket来实现了其本质,而对于真实开发中的python web程序来说,一般会分为两部分:服务器程序和应用程序。服务器程序负责对socket服务器进行封装,并在请求到来时,对请求的各种数据进行整理。应用程序则负责具体的逻辑处理。为了方便应用程序的开发,就出现了众多的Web框架,例如:Django、Flask、web.py 等。不同的框架有不同的开发方式,但是无论如何,开发出的应用程序都要和服务器程序配合,才能为用户提供服务。这样,服务器程序就需要为不同的框架提供不同的支持。这样混乱的局面无论对于服务器还是框架,都是不好的。对服务器来说,需要支持各种不同框架,对框架来说,只有支持它的服务器才能被开发出的应用使用。这时候,标准化就变得尤为重要。我们可以设立一个标准,只要服务器程序支持这个标准,框架也支持这个标准,那么他们就可以配合使用。一旦标准确定,双方各自实现。这样,服务器可以支持更多支持标准的框架,框架也可以使用更多支持标准的服务器。
WSGI
Web服务网关接口(Web Server Gateway Interface,简称“WSGI”)是一种在web服务器和Python web应用程序或框架之间的标准接口。具体的来说,WSGI是一种规范,它定义了使用python编写的web app与web server之间接口格式(Web服务器如何与Python应用程序进行交互),使得使用Python写的Web应用程序可以和Web服务器对接起来,实现web app与web server间的解耦。
通过标准化web服务器和Python web应用程序或框架之间的行为和通信,WSGI使得编写可移植的的Python web代码变为可能,使其能够部署在任何 WSGI可用的 web 服务器 上。WSGI的文档在 PEP 3333。
关于wsgi的更多信息:https://segmentfault.com/a/1190000003069785
python标准库提供的独立WSGI服务器称为wsgiref。
from wsgiref.simple_server import make_server def RunServer(environ, start_response): """ :param environ: 包含用户请求的所有信息,是wsgi封装好的 :param start_response: 一个发送HTTP响应的函数 """ start_response('200 OK', [('Content-Type', 'text/html')]) return [bytes('<h1>Hello, web!</h1>', encoding='utf-8'), ] if __name__ == '__main__': httpd = make_server('', 8000, RunServer) print("Serving HTTP on port 8000...") httpd.serve_forever() """ 详解environ, start_response参数 environ参数 environ参数是一个Python的字典,里面存放了所有和客户端相关的信息,这样application对象就能知道客户端请求的资源是什么,请求中带了什么数据等。environ字典包含了一些CGI规范要求的数据,以及WSGI规范新增的数据,还可能包含一些操作系统的环境变量以及Web服务器相关的环境变量。我们来看一些environ中常用的成员: 首先是CGI规范中要求的变量: REQUEST_METHOD: 请求方法,是个字符串,'GET', 'POST'等 SCRIPT_NAME: HTTP请求的path中的用于查找到application对象的部分,比如Web服务器可以根据path的一部分来决定请求由哪个virtual host处理 PATH_INFO: HTTP请求的path中剩余的部分,也就是application要处理的部分 QUERY_STRING: HTTP请求中的查询字符串,URL中?后面的内容 CONTENT_TYPE: HTTP headers中的content-type内容 CONTENT_LENGTH: HTTP headers中的content-length内容 SERVER_NAME和SERVER_PORT: 服务器名和端口,这两个值和前面的SCRIPT_NAME, PATH_INFO拼起来可以得到完整的URL路径 SERVER_PROTOCOL: HTTP协议版本,HTTP/1.0或者HTTP/1.1 HTTP_: 和HTTP请求中的headers对应。 WSGI规范中还要求environ包含下列成员: wsgi.version:表示WSGI版本,一个元组(1, 0),表示版本1.0 wsgi.url_scheme:http或者https wsgi.input:一个类文件的输入流,application可以通过这个获取HTTP request body wsgi.errors:一个输出流,当应用程序出错时,可以将错误信息写入这里 wsgi.multithread:当application对象可能被多个线程同时调用时,这个值需要为True wsgi.multiprocess:当application对象可能被多个进程同时调用时,这个值需要为True wsgi.run_once:当server期望application对象在进程的生命周期内只被调用一次时,该值为True 上面列出的这些内容已经包括了客户端请求的所有数据,足够application对象处理客户端请求了。 start_resposne参数 start_response是一个可调用对象,接收两个必选参数和一个可选参数: status: 一个字符串,表示HTTP响应状态字符串 response_headers: 一个列表,包含有如下形式的元组:(header_name, header_value),用来表示HTTP响应的headers exc_info(可选): 用于出错时,server需要返回给浏览器的信息 当application对象根据environ参数的内容执行完业务逻辑后,就需要返回结果给server端。我们知道HTTP的响应需要包含status,headers和body,所以在application对象将body作为返回值return之前,需要先调用start_response(),将status和headers的内容返回给server,这同时也是告诉server,application对象要开始返回body了。 """
Web 框架
Web 框架就是一份代码库,它能帮助开发人员创建可靠、可扩展、易维护的 Web 应用。
广义地说,web框架包含一系列库和一个主要的handler,这样你就能够构建自己的代码来实现web应用(比如说一个交互式的网站)。大多数web框架包含模式和工具,至少实现以下功能:
- URL路由(URL Routing)
- 将输入的HTTP请求匹配到特定的Python代码用来调用
- 请求和响应对象(Request and Response Objects)
- 封装来自或发送给用户浏览器的信息
- 模板引擎(Template Engine)
- 能够将实现应用的Python代码逻辑和其要产生输出的HTML(或其他)分离开
- web服务器开发(Development Web Server)
- 在开发机上运行HTTP服务器,从而快速开发;当文件更新时自动更新服务端代码。
通过python标准库提供的wsgiref模块开发一个自己的Web框架
from wsgiref.simple_server import make_server def index(): return 'index' def login(): return 'login' def routers(): # 路由函数 urlpatterns = ( ('/index/', index), ('/login/', login), ) return urlpatterns def RunServer(environ, start_response): # 响应头 start_response('200 OK', [('Content-Type', 'text/html')]) # 获取客户端url信息 url = environ['PATH_INFO'] # 执行路由函数,获取路由元组 urlpatterns = routers() func = None for item in urlpatterns: # 如果用户请求的url和我们定义的url匹配,将路由的url所对应的函数赋给func if item[0] == url: func = item[1] break if func: return func() # 执行里面的方法 else: return '404 not found' if __name__ == '__main__': httpd = make_server('', 8000, RunServer) print( "Serving HTTP on port 8000...") httpd.serve_forever()
模板
多数WSGI应用响应HTTP请求,从而服务于HTML或其他标记语言中的内容。关注点分离的概念建议我们使用模板,而不是直接由Python生成文本内容。模板引擎管理一系列的模板文件,其系统的层次性和包容性避免了不必要的重复。模板引擎负责渲染(产生)实际内容,用由应用生成的动态内容填充静态内容。
由于模板文件有时是由设计师或者前端开发者编写,处理不断增长的复杂度会变得困难。
一些通用的良好实践应用到了部分应用中,情景包括传递动态内容到模板引擎和模板自身中。
- 模板文件只应传递需要渲染的动态内容。避免传递附加的“以防万一”的内容: 需要时添加遗漏的变量比移除可能不用的变量要来的容易。
- 许多模板引擎允许在模板中编写复杂语句或者赋值,也有许多允许一些Python代码 在模板中等价编写。这种便利会导致复杂度不可控地增加,也使得查找bug变得更加 困难。
- 我们常常需要混合JavaScript模板和HTML模板。一种聪明的做法是孤立出HTML 模板传递部分变量内容到JavaScript代码中的部分。
Jinja2
Jinja2 是一个和Django模板系统类似的模板引擎,并有一些额外的特性。它是一种基于文本的模板语言,因此能够用于生成任何标记内容。它允许定制过滤(filter)、标签、测试和全局内容,不像在Django框架中实现的模板系统,它还允许调用函数
遵循jinja2的语法规则,其内部会对指定的语法进行相应的替换,从而达到动态的返回内容
在Jinja2中重要的html标签:
{# 这是注释 #} {# 下一个标签是输出变量: #} {{title}} {# 区块标签,能通过继承其他html代码来替换区块内容 #} {% block head %} <h1>This is the head!</h1> {% endblock %} {# 数组迭代输出 #} {% for item in list %} <li>{{ item }}</li> {% endfor %}
注意“{% ... %}"与"{{ .. }}", 前者用来执行一个循环或者一个赋值语句,后者用来打印一个变量。
from wsgiref.simple_server import make_server from jinja2 import Template def index(): # return 'index' # template = Template('Hello {{ name }}!') # result = template.render(name='John Doe') f = open('index.html') result = f.read() template = Template(result) data = template.render(name='John Doe', user_list=['alex', 'eric']) return data.encode('utf-8') def login(): # return 'login' f = open('login.html') data = f.read() return data def routers(): urlpatterns = ( ('/index/', index), ('/login/', login), ) return urlpatterns def run_server(environ, start_response): start_response('200 OK', [('Content-Type', 'text/html')]) url = environ['PATH_INFO'] urlpatterns = routers() func = None for item in urlpatterns: if item[0] == url: func = item[1] break if func: return func() else: return '404 not found' if __name__ == '__main__': httpd = make_server('', 8000, run_server) print("Serving HTTP on port 8000...") httpd.serve_forever()
<!DOCTYPE html> <html> <head lang="en"> <meta charset="UTF-8"> <title></title> </head> <body> <h1>{{name}}</h1> <ul> {% for item in user_list %} <li>{{item}}</li> {% endfor %} </ul> </body> </html> index.html
更多:http://docs.jinkan.org/docs/jinja2/
参考:http://www.cnblogs.com/wupeiqi/articles/5237672.html
参考:http://pythonguidecn.readthedocs.io/zh/latest/scenarios/web.html#