[翻译]Web框架是什么?
这篇文章纯粹是为了大致了解Web框架而进行的个人翻译,采用的方式是直译,原文地址:
https://jeffknupp.com/blog/2014/03/03/what-is-a-web-framework/
Web应用框架(简称Web框架),是用来构建web支持下的应用程序的实际方式。从简单的博客到复杂的富Ajax应用,web上的每个页面都是通过代码构建起来的。最近我发现很多对web框架(如Flask、Django)感兴趣的开发者没有真正地理解什么是web框架——它们的目的是什么、它们怎么运行。因此,我将在本文中讨论web框架基础这个经常被忽略的话题。通读本文,你会对什么web框架、为什么它们一开始就存在等问题有深入的理解,这也会让你学习一个新的web框架以及使用哪个框架时的决定大为轻松。
Web如何工作
在我们讨论具体的框架以前,需要理解web如何工作,为此我们将深入探讨当你在浏览器中输入URL地址并按下回车键时到你的浏览器在呈现页面的过程中经过的步骤(不包括DNS查表)。
Web服务器及web提供的服务
每个页面都是以HTML
传递到你的浏览器中的,HTML
是一种浏览器用来描述内容和页面结构的一种语言,而把HTML
发送到你的浏览器中的应用程序就叫Web服务器。令人困惑的是,这个应用程序所在的机器也叫做Web服务器。
HTTP
浏览器使用HTTP
协议(协议,在编程领域中是通信双方约定的数据格式和通信步骤)从Web服务器(或叫应用程序服务器)中下载页面,HTTP
协议基于请求—响应
模型,客户端(你的浏览器)向在运行在一台物理机器上的网页应用程序请求数据,网页应用程序接着就用你浏览器请求的数据来响应这个请求。
有一点需要记住的是,通信总是由客户端(你的浏览器)发起的,服务器(这里是网页服务器)没有任何方式发起一场连接或者主动给你的浏览器发送未请求的信息,如果你从一个网页服务器中收到了数据,那一定是因为你的浏览器发出了请求。
HTTP方法
在HTTP
协议中的每一个信息都有相关的方法(或动作),各不相同的HTTP
方法对应于客户端根据不同的需要而发出的不同逻辑的请求,比如请求一个网页的HTML就和提交一个表单在逻辑上不一样,所以处理这两种的请求就需要不同的方法。
HTTP GET
GET
方法做的事就跟它说的一样:从网页服务器要求(请求)数据,GET
请求是目前最常见的HTTP
请求,在一个GET
请求过程中,网页应用程序除了将所需要页面的HTML响应给这个请求外不做任何其他事情。这里特意指出,在处理GET
请求过程中网页应用程序不应该改变自身任何状态(比如,它不能基于一个GET
请求就创建一个新的用户帐户),因为这个原因,GET
请求通常被认为是“安全”的,因为它们不会导致驱动网站的应用程序的任何变化。
HTTP POST
显然,除了单纯地看看页面意外还有更多与网站交互的方式,我们也可以向web应用程序发送数据,比如说一个表单,要完成这个工作,需要另一个不同的请求:POST
。POST
请求通常会携带用户输入的数据,继而会引起web应用内部采取一些行为。在网站的表单上输入你的信息来注册就是通过POST
请求把表单上的数据传递给web应用的。
与GET
请求不同,POST
请求通常会导致web应用的状态改变,在上面的例子中,当一个表单被POST
ed以后,就创建了一个新的用户帐户,其次,POST
请求也不总是会让一个新的HTML页面发送给客户端,客户端是通过响应的响应码来决定服务器上的操作是否进行顺利(大概是说,响应post请求的不是HTML页面,而是响应码)。
HTTP 响应码 Response Codes
在最常见的情况中,web服务器会返回一个响应码200,意思是“我做了你要求做的,并且一切进行顺利”,响应码总是一个三位的数字。web应用必须为每个响应发送一个响应码来表明对一个请求的处理情况。200
意为“OK”,并且是响应一个GET
请求最常用到的,而一个POST
请求则有可能会让一个204
(“没有内容”)响应回去,意思是“一切事情都干得很顺利,但我没啥可以呈现给你看的”。
值得注意的是,POST
请求还会被发送到一个与提交表单的网页不同的URL,用上面那个注册用户的例子来说,就是表单所在的网址是www.foo.com/signup
,你在这个页面点击了提交
,而这个POST
请求会发送到www.foo.com/process_signup
,POST
请求被发送到的地址对于表单的HTML
是特定的。
Web应用程序
只要用到HTTP
GET
和POST
,你就能够做很多事情,因为它们是最常用的HTTP
方法。web应用就是用来接受HTTP
请求并且用通常包含HTML表示的请求页面的HTTP
响应进行回复。POST
请求引起web应用产生一些行为,如在数据库里添加一条新的记录。还有很多其他HTTP
方法,但目前我们仅聚焦于GET
和POST
方法。
最简单的web应用长什么样?我们可以写一个监听80
端口(一个几乎所有HTTP
通信都送到这个端口)连接的应用,一旦它监听到了一个连接,它会等待客户端发送一个请求,然后它会用非常简单的HTML进行回复。
下面是这个应用的情况:
import socket
HOST = ''
PORT = 80
listen_socket =
socket.socket(socket.AF_INET,
socket.SOCK_STREAM)
listen_socket.bind((HOST, PORT))
listen_socket.listen(1)
connection, address =
listen_socket.accept()
request = connection.recv(1024)
connection.sendall("""HTTP/1.1 200 OK
Content-type: text/html
<html>
<body>
<h1>Hello, World!</h1>
</body>
</html>""")
connection.close()
(如果上述代码不工作,将PORT
改成8080
试试)
上述代码接收了一个简单连接和一个简单请求,不管请求什么URL,它会回复一个HTTP 200
的响应(它不是一个真正意义上的web服务器),Content-type: text/html
这一行代表一个header领域,header用来提供请求或者响应的元信息,在这个例子中,我们告诉客户端,发送过去的数据是HTML(而不是JSON)
对一个请求的分析
仔细观察我用来测试上述程序的HTTP
请求,我发现它和响应很相似,第一行的格式是
<HTTP Method> <URL> <HTTP version>
在本例中,是GET / HTTP/1.1
,在第一行接下来是诸如Accept: */*
(意思是我们接收任何响应里面的内容)的头部,这是一个请求的基本情况。
我们发送的响应有着类似格式,如
<HTTP version> <HTTP Status-Code> <Status-Code Reason-Phrase>
在本例中,是HTTP/1.1 200 OK
,接下来headers,格式跟请求的headers一样,最后包含了响应的实际内容。注意这些都可以用一个字符串或者二进制对象进行编码,Content-type
header让客户端知道如何解释响应。
web服务器的fatigue
如果我们接着上述的例子继续讲解web应用,随之而来有很多问题需要解决:
- 我们要怎么检测所需要的URL并且返回合适的网页?
- 除了简单的
GET
请求以外,我们如何处理POST
请求? - 我们怎么处理一些像sessions和cookies等更高级的概念?
- 我们如何描述能够处理数以千计并发连接的应用?
如你能想象,没有人愿意每次构建服务器时都要逐一对付这些问题,因此就有了能够处理HTTP
协议细节和统一解决上述问题的包。然而要记住,它们的核心就跟我们上述提到的例子一样:监听请求并且发送带有HTML的HTTP
响应回去。
注意client-side web 框架是另一个不同的庞然大物,与我们上述讲到的大不相同。
解决两个主要问题:路由与模版
在构建一个web应用涉及到的一切问题中,有两个是重中之重:
- 我们如何将一个被请求的URL定位到用于处理它的代码?
- 我们如何动态创建被请求的HTML,在其中加入从数据库读取的计算值或信息?
每个web框架都用某些方式来解决这些问题,并且有很多不同的方法。接下来我讨论了Django和Flask用来解决这些问题的方式,首先我们要简要讨论MVC架构。
Django中的MVC
Django遵从MVC架构并且要求使用该框架的代码也要使用该架构,MVC即“模型—视图—控制 (Model-View-Controller)”的缩写,用来表示web应用需要负责的不同方面。诸如数据库这样的资源是用模型来表示(类似的,Python中常用class
来表示一些真实世界的对象),控制包含了应用的业务逻辑和对模型的操作,视图会接收所有用来动态生成HTML页面所需要的信息。
令初学者疑惑的是,在Django中,MVC架构中的控制叫做视图,视图叫做模版 templates,除去命名上的古怪,Django是非常典型的MVC架构的部署方式。
Django中的路由
路由是将被请求的URL定位到负责生成相关HTML的代码的过程,最简单的例子是所有请求都用同一个代码进行处理(如我们之前所举的例子),稍微复杂一点,每一个URL按照1:1地对应到视图函数
中,比如,我们可以在某个代码来实现如下功能,如果URLwww.foo.com/bar
被请求了,然后让handle_bar()
函数来负责处理进行响应,以此类推,我们可以为所有web应用支持的URLs建立对应的处理函数。
但是,如果URLs中包含了有用的数据,比如某个资源的ID(按上面的例子来说,如果URL是www.foo.com/users/3/
),这种路由方法就会失败,那么我们怎么样将URL对应到一个视图函数的同时呈现ID为3
的用户页面呢?
Django的处理方式是将URL正则表达式定位到能够接受参数的视图函数,举例来说,我可以说符合^/users/(?P<id>\d+)/$
格式的URLs会调用display_user(id)
函数,函数中的id
变量会用正则表达式中的id
进行替换,通过这种处理,任何/users/<some number>/
格式的URL会定位到display_user
函数中,这些正则表达式可以写得非常复杂,并且同时包含键盘和位置参数。
Flask中的路由
Flask则用了不太一样的处理方式,正统的方法是通过使用route()
修饰器将一个被请求的URL和函数连接起来。下面的Flask代码跟上面提到的正则表达式与函数的功能相同:
@app.route('/users/<id:int>/')
def display_user(id):
# ...
如你所见,修饰器起到了简化正则表达式的形式来将URLs对应到变量中(使用/
作为分离器),参数通过包括一个传递到route()
的URL中的<name:type>
命令被获取,路由到像/info/about_us.html
之类的静态URLs的方式也不难想到:
@app.route('/info/about_us.html')
通过模版生成HTML
继续上面的例子,我们一旦将合适的代码定位到正确的URL以后,我们怎样动态生成支持web开发者修改的HTML呢?Django和Flask的处理方式都是通过HTML 模版
HTML 模版类似于使用str.format()
,先通过占位符来写需要的输出,后面可以被替换成传入str.format()
函数的变量,想象一下将整个网页写成一个字符串,用括号标记动态数据,最后调用str.format()
,Django模版和Flask使用的模版引擎jinja2都是这么工作的。
然而,不是所有的模版引擎都进行相同的创建,Django对于模版编程提供了基本支持,而Jinja2基本上让你自由发挥(不一定准确,但基本上是这个意思)。Jinja2会缓存绘制模版的结果,这样接下来如果有相同变量的请求则会直接从缓存中返回,而不是重新绘制。
服务器交互
Django由于其“一应俱全 batteries included”哲学,包含了一个ORM
(object relational mapper, 对象关系映射器),ORM
的目的有两层:将Python类定位到数据库表,并且把各种不同数据库引擎的差别抽象掉(前者是其最基本的功能)。人们都不太喜欢ORMs(由于这种定位从来做不到完善),但是还是可以接受的。Django是功能齐备的,相比之下,Flask作为一个“微框架”则没有ORM(与Django最大也是唯一的竞争者SQLAlchemy相同)
由于囊括了一个ORM
,Django得以创建全功能CRUD
应用,CRUD
(Create Read Update Delete)应用似乎是web框架的有效切入点(从服务器端来看)。Django(以及Flask-SQLAlchemy)为每个模型创建了很多不同的CRUD
操作。
Web框架总结
到目前为之,web框架的目的应该已经明确了:作为HTTP
请求响应和相应底层代码之间的接口,即把底层代码给隐藏起来,藏到什么程度就看不同的框架如何处理了。Django和Flask代表了两种极端,Django几乎涉及到了每种情况,这都快成为它的弊端了,Flask将自己定位为一个“微框架”,它仅保留了web框架最核心的规模,而依赖于第三方包来处理那些web框架不太常用的任务。
记住,所有Python web框架工作的本质都是一样的:它们接收HTTP
请求,将之分配到生成HTML的代码中,并且用相应内容创建HTTP
响应,实际上,所有主流服务器端的框架都这样进行工作(包括JavaScript 框架)。通过上文了解了它们的目的后,希望你现在对web框架进行选择时能心里有数。
原文地址:https://jeffknupp.com/blog/2014/03/03/what-is-a-web-framework/