什么是 Web 框架?

可不要被名字迷惑,它可不是web网页的框架,而是服务器用来产生web网页时用到的工具。


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协议基于请求—响应模型,客户端(你的浏览器)向在运行在一台物理机器上的网页应用程序请求数据,web应用程序接着就用你浏览器请求的数据来响应这个请求。

有一点需要记住的是,通信总是由客户端(你的浏览器)发起的,服务器(这里是web服务器)没有任何方式发起连接或者主动给你的浏览器发送未请求的信息,如果你从一个网页服务器中收到了数据,那一定是因为你的浏览器发出了请求。

HTTP方法

HTTP协议中的每一个信息都有相关的方法(或动作),各不相同的HTTP方法对应于客户端根据不同的需要而发出的不同逻辑的请求,比如请求一个网页的HTML就和提交一个表单在逻辑上不一样,所以处理这两种的请求就需要不同的方法。

HTTP GET

GET方法做的事就跟它说的一样:从网页服务器要求(请求)数据,GET请求是目前最常见的HTTP请求,在一个GET请求过程中,网页应用程序除了将所需要页面的HTML响应给这个请求外不做任何其他事情。这里特意指出,在处理GET请求过程中网页应用程序不应改变自身任何状态(比如,它不能基于一个GET请求就创建一个新的用户帐户),因为这个原因,GET请求通常被认为是“安全”的,因为它们不会导致驱动网站的应用程序的任何变化。

HTTP POST

显然,除了单纯地看看页面意外还有更多与网站交互的方式,我们也可以向web应用程序发送数据,比如说一个表单,要完成这个工作,需要另一个不同的请求:POSTPOST请求通常会携带用户输入的数据,继而会引起web应用采取一些行为。在网站的表单上输入你的信息来注册就是通过POST请求把表单上的数据传递给web应用的。

GET请求不同,POST请求通常会导致web应用的状态改变,在上面的例子中,当一个表单被POST以后,就创建了一个新的用户帐户,其次,POST请求也不总是会让一个新的HTML页面发送给客户端,客户端通过响应的响应码来决定服务器上的操作是否进行顺利。

HTTP 响应码

在最常见的情况中,web服务器会返回一个响应码200,意思是“我做了你要求做的,并且一切进行顺利”,响应码总是一个三位的数字。web应用必须为每个响应发送一个响应码来表明对一个请求的处理情况。200意为“OK”,并且是响应一个GET请求最常用到的,而一个POST请求则有可能返回响应码204(“没有内容”),意思是“一切事情都干得很顺利,但我没啥可以呈现给你看的”。

值得注意的是,POST请求还会被发送到一个与提交表单的网页不同的URL,用上面那个注册用户的例子来说,就是表单所在的网址是www.foo.com/signup,你在这个页面点击了提交,而这个POST请求会发送到www.foo.com/process_signupPOST请求被发送到的地址对于表单的HTML是特定的。

Web应用程序

只要用到HTTP GETPOST,你就能够做很多事情,因为它们是最常用的HTTP方法。web应用就是用来接受HTTP请求并且用通常包含HTML表示的请求页面的HTTP响应进行回复。POST请求引起web应用产生一些行为,如在数据库里添加一条新的记录。还有很多其他HTTP方法,但目前我们仅聚焦于GETPOST方法。

最简单的web应用长什么样?我们可以写一个监听80端口连接的应用,一旦它监听到了一个连接,它会等待客户端发送一个请求,然后它会用非常简单的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-typeheader让客户端知道如何解释响应。

web服务器的fatigue

如果我们接着上述的例子继续讲解web应用,随之而来有很多问题需要解决:

  1. 我们要怎么检测所需要的URL并且返回合适的网页?
  2. 除了简单的GET请求以外,我们如何处理POST请求?
  3. 怎么处理一些像sessions和cookies等更高级的概念?
  4. 如何描述能够处理数以千计并发连接的应用?

如你能想象,没有人愿意每次构建服务器时都要逐一对付这些问题,因此就有了能够处理HTTP协议细节和统一解决上述问题的包。然而要记住,它们的核心就跟我们上述提到的例子一样:监听请求并且发送带有HTML的HTTP响应

注意客户端 web 框架(如前端当前流行的三大框架:React、Vue以及Angular)是另一个不同的庞然大物,与我们上述讲到的大不相同。

解决两个主要问题:路由与模版

在构建一个web应用涉及到的一切问题中,有两个是重中之重:

  1. 如何将一个被请求的URL定位到用于处理它的代码?
  2. 如何动态创建被请求的HTML,在其中加入从数据库读取的计算值或信息?

每个web框架都用某些方式来解决这些问题,并且有很多不同的方法。接下来我讨论了Django和Flask用来解决这些问题的方式,首先我们要简要讨论MVC架构。

Django中的MVC

Django遵从MVC架构并且要求使用该框架的代码也使用该架构,MVC即“模型—视图—控制 (Model-View-Controller)”的缩写,用来表示web应用需要负责的不同方面。与数据库有关的资源是用模型来表示(类似的,Python中常用class来表示一些真实世界中的对象),控制包含应用的业务逻辑和对模型的操作,视图接受所有用来动态生成HTML页面所需要的信息。

令初学者疑惑的是,在Django中,MVC架构中的控制叫做视图视图叫做模版,除去命名上的古怪,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响应,实际上,所有主流服务器端的框架都这样进行工作(包括Nodejs 的框架)。通过上文了解了它们的目的后,希望你现在对web框架进行选择时能心里有数。

原文地址:https://jeffknupp.com/blog/2014/03/03/what-is-a-web-framework/

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 203,362评论 5 477
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 85,330评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 150,247评论 0 337
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,560评论 1 273
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,580评论 5 365
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,569评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,929评论 3 395
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,587评论 0 258
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,840评论 1 297
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,596评论 2 321
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,678评论 1 329
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,366评论 4 318
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,945评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,929评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,165评论 1 259
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 43,271评论 2 349
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,403评论 2 342

推荐阅读更多精彩内容