最早的软件都是运行在大型机上的,软件使用者通过“哑终端”登陆到大型机上去运行软件。后来随着PC机的兴起,软件开始主要运行在桌面上,而数据库这样的软件运行在服务器端,这种Client/Server模式简称CS架构。
随着互联网的兴起,人们发现,CS架构不适合Web,最大的原因是Web应用程序的修改和升级非常迅速,而CS架构需要每个客户端逐个升级桌面App,因此,Browser/Server模式开始流行,简称BS架构。
在BS架构下,客户端只需要浏览器,应用程序的逻辑和数据都存储在服务器端。浏览器只需要请求服务器,获取Web页面,并把Web页面展示给用户即可。
当然,Web页面也具有极强的交互性。由于Web页面是用HTML编写的,而HTML具备超强的表现力,并且,服务器端升级后,客户端无需任何部署就可以使用到新的版本,因此,BS架构迅速流行起来。
Web应用开发可以说是目前软件开发中最重要的部分。Web开发也经历了好几个阶段:
Python的诞生历史比Web还要早,由于Python是一种解释型的脚本语言,开发效率高,所以非常适合用来做Web开发。Python有上百种Web开发框架,有很多成熟的模板技术,选择Python开发Web应用,不但开发效率高,而且运行速度快。
在Web应用中,服务器把网页传给浏览器,实际上就是把网页的HTML代码发送给浏览器,让浏览器显示出来。而浏览器和服务器之间的传输协议是HTTP,所以:
GET
还是POST
,GET
仅请求资源,POST
会附带用户数据;/full/url/path
;Host: www.sina.com.cn
POST
,那么请求还包括一个Body
,包含用户数据。200
表示成功,3xx
表示重定向,4xx
表示客户端发送的请求有错误,5xx
表示服务器端处理时发生了错误;Content-Type
指定,例如:Content-Type: text/html;charset=utf-8
表示响应类型是HTML文本,并且编码是UTF-8
,Content-Type: image/jpeg
表示响应类型是JPEG格式的图片;Web采用的HTTP协议采用了非常简单的请求-响应模式,从而大大简化了开发。当我们编写一个页面时,我们只需要在HTTP响应中把HTML发送出去,不需要考虑如何附带图片、视频等,浏览器如果需要请求图片和视频,它会发送另一个HTTP请求,因此,一个HTTP请求只处理一个资源。
HTTP协议同时具备极强的扩展性,虽然浏览器请求的是http://www.sina.com.cn/
的首页,但是新浪在HTML中可以链入其他服务器的资源,比如,从而将请求压力分散到各个服务器上,并且,一个站点可以链接到其他站点,无数个站点互相链接起来,就形成了World Wide Web,简称"WWW"。
每个HTTP请求和响应都遵循相同的格式,一个HTTP包含Header和Body两部分,其中Body是可选的。
HTTP协议是一种文本协议,所以,它的格式也非常简单。HTTP GET请求的格式:
GET /path HTTP/1.1
Header1: Value1
Header2: Value2
Header3: Value3
每个Header一行一个,换行符是\r\n
。
HTTP POST请求的格式:
POST /path HTTP/1.1
Header1: Value1
Header2: Value2
Header3: Value3
body data goes here...
当遇到连续两个\r\n
时,Header部分结束,后面的数据全部是Body。
HTTP响应的格式:
200 OK
Header1: Value1
Header2: Value2
Header3: Value3
body data goes here...
HTTP响应如果包含body,也是通过\r\n\r\n
来分隔的。请再次注意,Body的数据类型由Content-Type
头来确定,如果是网页,Body就是文本,如果是图片,Body就是图片的二进制数据。
当存在Content-Encoding
时,Body数据是被压缩的,最常见的压缩方式是gzip,所以,看到Content-Encoding: gzip
时,需要将Body数据先解压缩,才能得到真正的数据。压缩的目的在于减少Body的大小,加快网络传输。
最简单的HTML:
<html>
<head>
<title>Hellotitle>
head>
<body>
<h1>Hello, world!h1>
body>
html>
可以用文本编辑器编写HTML,然后保存为hello.html
,双击或者把文件拖到浏览器中,就可以看到效果。
HTML文档就是一系列的Tag组成,最外层的Tag是。规范的HTML也包含
...
和...
(注意不要和HTTP的Header、Body搞混了),由于HTML是富文档模型,所以,还有一系列的Tag用来表示链接、图片、表格、表单等等。
CSS是Cascading Style Sheets(层叠样式表)的简称,CSS用来控制HTML里的所有元素如何展现,比如,给标题元素加一个样式,变成48号字体,灰色,带阴影:
Hello
Hello, world!
JavaScript虽然名称有个Java,但它和Java真的一点关系没有。JavaScript是为了让HTML具有交互性而作为脚本语言添加的,JavaScript既可以内嵌到HTML中,也可以从外部链接到HTML中。如果希望当用户点击标题时把标题变成红色,就必须通过JavaScript来实现:
<html>
<head>
<title>Hello</title>
<style>
h1 {
color: #333333;
font-size: 48px;
text-shadow: 3px 3px 3px #666666;
}
</style>
<script>
function change() {
document.getElementsByTagName('h1')[0].style.color = '#ff0000';
}
</script>
</head>
<body>
<h1 onclick="change()">Hello, world!</h1>
</body>
</html>
Web应用的本质就是:
所以,最简单的Web应用就是先把HTML用文件保存好,用一个现成的HTTP服务器软件,接收用户请求,从文件中读取HTML,返回。Apache、Nginx、Lighttpd等这些常见的静态服务器就是干这件事情的。
如果要动态生成HTML,就需要把上述步骤自己来实现。若自己来写这些底层代码,时间成本太高。正确的做法是底层代码由专门的服务器软件实现,用Python专注于生成HTML文档。因为我们不希望接触到TCP连接、HTTP原始请求和响应格式,所以,需要一个统一的接口,让我们专心用Python编写Web业务。
这个接口就是WSGI:Web Server Gateway Interface。
WSGI接口定义非常简单,它只要求Web开发者实现一个函数,就可以响应HTTP请求。一个最简单的Web版本的“Hello, web!”:
def application(environ, start_response):
start_response('200 OK', [('Content-Type', 'text/html')])
return [b'Hello, web!
']
上面的application()
函数就是符合WSGI标准的一个HTTP处理函数,它接收两个参数:
dict
对象;在application()
函数中,调用:
start_response('200 OK', [('Content-Type', 'text/html')])
就发送了HTTP响应的Header,注意Header只能发送一次,也就是只能调用一次start_response()
函数。start_response()
函数接收两个参数,一个是HTTP响应码,一个是一组list
表示的HTTP Header,每个Header用一个包含两个str
的tuple
表示。
通常情况下,都应该把Content-Type
头发送给浏览器。其他很多常用的HTTP Header也应该发送。
然后,函数的返回值b'
将作为HTTP响应的Body发送给浏览器。Hello, web!
'
有了WSGI,我们关心的就是如何从environ
这个dict
对象拿到HTTP请求信息,然后构造HTML,通过start_response()
发送Header,最后返回Body。
application()
函数必须由WSGI服务器来调用,因为如果自己调用,两个参数environ
和start_response
没法提供,返回的bytes
也没法发给浏览器。有很多符合WSGI规范的服务器,我们可以挑选一个来用。
Python内置了一个WSGI服务器,这个模块叫wsgiref,它是用纯Python编写的WSGI服务器的参考实现。所谓“参考实现”是指该实现完全符合WSGI标准,但是不考虑任何运行效率,仅供开发和测试使用。
先编写hello.py
,实现Web应用程序的WSGI处理函数:
# hello.py
def application(environ, start_response):
start_response('200 OK', [('Content-Type', 'text/html')])
return [b'Hello, web!
']
然后,再编写一个server.py
,负责启动WSGI服务器,加载application()
函数:
# server.py
# 从wsgiref模块导入:
from wsgiref.simple_server import make_server
# 导入我们自己编写的application函数:
from hello import application
# 创建一个服务器,IP地址为空,端口是8000,处理函数是application:
httpd = make_server('', 8000, application)
print('Serving HTTP on port 8000...')
# 开始监听HTTP请求:
httpd.serve_forever()
确保以上两个文件在同一个目录下,然后在命令行输入python server.py
来启动WSGI服务器。
注意:如果8000
端口已被其他程序占用,启动将失败,请修改成其他端口。
启动成功后,打开浏览器,输入http://localhost:8000/
,就可以看到结果了。
在处理很多个不同的URL时,代码不好维护。WSGI提供的接口虽然比HTTP接口高级了不少,但和Web App的处理逻辑比,还是比较低级,我们需要在WSGI接口之上能进一步抽象,让我们专注于用一个函数处理一个URL,至于URL到函数的映射,就交给Web框架来做。
由于用Python开发一个Web框架十分容易,所以Python有上百个开源的Web框架。在此选择一个比较流行的Web框架——Flask来使用。若想进行详细学习,可参考:用Python进行Web开发——Flask框架入门学习
用Flask编写Web App比WSGI接口简单,先安装Flask。
然后写一个app.py
,处理3个URL,分别是:
GET /
:首页,返回Home;GET /signin
:登录页,显示登录表单;POST /signin
:处理登录表单,显示登录结果。Flask通过Python的装饰器在内部自动地把URL和函数给关联起来,写出来的代码就像这样:
from flask import Flask
from flask import request
app = Flask(__name__)
@app.route('/', methods=['GET', 'POST'])
def home():
return 'Home
'
@app.route('/signin', methods=['GET'])
def signin_form():
return ''''''
@app.route('/signin', methods=['POST'])
def signin():
# 需要从request对象读取表单内容:
if request.form['username']=='admin' and request.form['password']=='password':
return 'Hello, admin!
'
return 'Bad username or password.
'
if __name__ == '__main__':
app.run()
运行python app.py
,Flask自带的Server在端口5000
上监听:
$ python app.py
* Running on http://127.0.0.1:5000/
除了Flask,常见的Python Web框架还有:
Web App不仅仅是处理逻辑,展示给用户的页面也非常重要。由于在Python代码里拼字符串是不现实的,所以,模板技术出现了。
使用模板,需要预先准备一个HTML文档,这个HTML文档不是普通的HTML,而是嵌入了一些变量和指令,然后,根据传入的数据,替换后,得到最终的HTML,发送给用户:
这就是传说中的MVC:Model-View-Controller,中文名“模型-视图-控制器”。
{{ name }}
的模板就是V:View,View负责显示逻辑,通过简单地替换一些变量,View最终输出的就是用户看到的HTML。dict
:{ 'name': 'Michael' }
。只是因为Python支持关键字参数,很多Web框架允许传入关键字参数,然后,在框架内部组装出一个dict
作为Model。把上次直接输出字符串作为HTML的例子用MVC模式改写一下:
from flask import Flask, request, render_template
app = Flask(__name__)
@app.route('/', methods=['GET', 'POST'])
def home():
return render_template('home.html') # 实现模板的渲染
@app.route('/signin', methods=['GET'])
def signin_form():
return render_template('form.html') # 实现模板的渲染
@app.route('/signin', methods=['POST'])
def signin():
username = request.form['username']
password = request.form['password']
if username=='admin' and password=='password':
return render_template('signin-ok.html', username=username)
return render_template('form.html', message='Bad username or password', username=username)
if __name__ == '__main__':
app.run()
Flask默认支持的模板是jinja2,所以先直接安装jinja2:
编写jinja2模板:
home.html 用来显示首页的模板:
<html>
<head>
<title>Hometitle>
head>
<body>
<h1 style="font-style:italic">Homeh1>
body>
html>
form.html 用来显示登录表单的模板:
<html>
<head>
<title>Please Sign Intitle>
head>
<body>
{% if message %}
<p style="color:red">{{ message }}p>
{% endif %}
<form action="/signin" method="post">
<legend>Please sign in:legend>
<p><input name="username" placeholder="Username" value="{{ username }}">p>
<p><input name="password" placeholder="Password" type="password">p>
<p><button type="submit">Sign Inbutton>p>
form>
body>
html>
signin-ok.html 登录成功的模板:
<html>
<head>
<title>Welcome, {{ username }}title>
head>
<body>
<p>Welcome, {{ username }}!p>
body>
html>
在form.html
中加了一点条件判断,把form.html
重用为登录失败的模板。
最后,一定要把模板放到正确的templates
目录下,templates
和app.py
在同级目录下。
启动python app.py
,看看使用模板的页面效果:
通过MVC,我们在Python代码中处理M:Model和C:Controller,而V:View是通过模板处理的,这样,我们就成功地把Python代码和HTML代码最大限度地分离了。
在Jinja2模板中,用{{ name }}
表示一个需要替换的变量。很多时候,还需要循环、条件判断等指令语句,在Jinja2中,用{% ... %}
表示指令。
除了Jinja2,常见的模板还有:
<% ... %>
和${xxx}
的一个模板;<% ... %>
和${xxx}
的一个模板;{% ... %}
和{{ xxx }}
的模板。有了MVC,我们就分离了Python代码和HTML代码。HTML代码全部放到模板里,写起来更有效率。
参考资料:
https://www.liaoxuefeng.com/wiki/1016959663602400/1017804650182592