文章目录
web开发发展历程
Python有上百种web开发框架,有很多成熟的模块技术,用Python进行web开发,开发效率高,运行速度快。
HTTP协议简介
在web应用中,服务器将网页传给服务器,实质上就是将网页的HTML代码发送给浏览器,让浏览器进行显示。浏览器和服务器之间的传输协议是HTTP,所以:
- HTML是一种用于定义网页的文本文档;
- HTTP是在网络上传输HTML的协议,用于浏览器和服务器之间的通信
实验环境
HTTP内容简介
- 打开开发者工具
在浏览器右上角处单击纵向排列的三个点—更多工具—开发者工具,或者通过快捷键optioncommondI打开:
Elements
显示了网页的结构,Network
显示浏览器和服务器之间的通信。 - 记录浏览器和服务器之间的通信
确保Network
下的小红灯亮着,灯亮即表示Chrome在记录server和browser之间的通信
- 访问网页
在地址栏输入在这里插入代码片www.sina.com.cn
,查阅浏览器的记录以了解浏览器的工作
Request Headers
首先,定位到Networks的第一条记录,在右侧的Request Headers
右边单击view source
,查看浏览器发送给服务器的请求:
GET / HTTP/1.1
GET
表示一个读取请求,浏览器请求从服务器获得网页的数据,/
表示URL的路径。URL总是以/
开头。表示首页。最后的HTTP/1.1
表示采用的HTTP协议版本为1.1.目前版本为1.1,但大部分的服务器也支持1.1版本,1.1和1.0版本的区别在,于1.1版本允许多个HTTP请求复用一个TCP连接以加快传输速度。
Host: www.sina.com.cn
该行表示请求的域名是www.sina.com.cn
。如果一台服务器有多个网站,服务器就需要使用Host
来区分浏览器的请求。
Response Headers
同样是本条记录,在右侧的ResponseHeaders
右边单击view source
,查看服务器返回给浏览器的原始响应数据:
HTTP响应分为Header和body两个部分,我们看到最重要的几行如下:
HTTP/1.1 302 Moved Temporarily
请求相应的状态,正确的响应应为HTTP/1.1 200 OK
,失败的响应有404 Not Found
:网页不存在,500 Internal Server Error
:服务器内部错误,等等。
Content-Type: text/html
响应的内容,此处为text/html
,表示HTML网页,浏览器依据Content-Type
来判断响应的内容是网页、图片、视频还是音乐。因此,即便URL是http://example.com/abc.jpg
,该内容也不一定是图片。
HTTP响应的body就是HTML源码,我们在开发者工具下点击源码
选项就可以在浏览器中查看HTML源码:
当浏览器读取到新浪首页的HTML源码之后,它会解析HTML,显示页面,然后根据HTML里面的各种链接发送HTTP请求给新浪服务器,获取相应的图片、视频、flash、JavaScript脚本、CSS等资源,最终呈现出一个完整的页面。
HTTP请求
web采用的HTTP协议使用了非常简单的请求-响应模式,大大简化了开发流程。当我们编写页面是,只需要在HTTP响应中把HTML发送出去,而不需要考虑如何附带图片、视频等内容,浏览器如果需要请求图片和视频等内容,它会发送另一个HTTP请求。因此,一个HTTP请求只处理一个资源。
HTTP协议具备极强的扩展性,虽然浏览器请求的是http://www.sina.com.cn
但是新浪在HTML中可以链入其他服务器的资源,如`,从而你将请求压力分散到各个服务器上,并且一个站点可以链接到其他站点,无数个站点互相链接起来,形成了world wide web简称“www"。
HTTP格式
每个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数据是被压缩的,最常见的压缩方式是zip
。如果看到Content-Encoding:gzip
时,需要先将body数据进行解压缩,才能得到真正的数据,压缩的目的在于减少body的大小,加块网络传输。
推荐文档
如果要详细了解HTTP协议,推荐书籍HTTP: The Definitive Guide,中文译本HTTP权威指南
HTML简介
超文本标记语言:HyperText Markup Language,HTML。HTML定义了一套语法规则来对浏览器接收到的源码进行解析。
下图是一个简单的HTML
我们可以用编辑一个.txt
文档,然后将后缀改为.html
,用浏览器打开,就可以看到我们编辑的文档
HTML文档是由一系列的tag组成,最外层的tag是<html>
…</html>
。规范的HTML也包含<head>
…</head>
和<body
…</body>
。注意,这里的head和body和HTTP的header和body不一样的。因为HTML是富文档模型,所以还有一系列tag来表示链接、图片、表格、表单等。
CSS简介
Cascading Style Sheets,层叠样式表,用来控制HTML里面的元素的展现方式,比如给标题元素添加样式,改变字号、颜色等。
JavaScript简介
JavaScript是为了让HTML具有交互性而作为脚本语言添加的,JavaScript既可以内嵌到HTML中,也可从外部链接到HTML。如果我们希望用户点击标题时把标题变成红色,就必须通过JavaScript来实现
单击标题后的效果:
小结
- HTML定义页面内容
- CSS控制页面元素的样式
- JavaScript控制页面的交互逻辑
推荐上w3school具体学习
当我们用Python或是其他语言开发web应用,我们就要在服务器端动态创建出HTML,浏览器就会向不同的用户显示出不同的web页面。
WSGI接口简介
web应用的本质:
- 浏览器发送一个HTTP请求;
- 服务器收到请求生成HTML文档;
- 服务器将HTML文档作为HTTP相应的body发送给浏览器;
- 浏览器收到HTTP响应从HTTP的body中取出HTML文档并显示。
所以,最简单的web应用就是先把HTML用文件保存好,用一个现成的HTTP服务器软件接受用户请求,从文件中读取HTML返回。Apache、Nginx、Lighttpd等常见的静态服务器就是负责这些工作的。
如果要动态生成HTML,正确的做法是,底层代码由专门的服务器软件实现,我们用Python专注于生成HTML文档,因为我们不希望接触到TCP连接、HTTP请求和响应格式,所以需要一个统一的接口来让我们编写web业务。
WSGI:Web Server Gateway Interface,这个接口定义非常简单,只要求web开发者实现一个函数,就可以响应HTTP请求。
让我们来看一个简单的例子:
def application(environ, start_response):
start_response('200 OK', [('Content-Type', 'text/html')])
return [b'<h1>喵呜嗷~这是一个HTML喔</h1>']
application()
其中,application()函数是一个符合WSGI标准的HTTP处理函数,它接收两个参数:
- environ:一个包含HTTP请求信息的
dict
对象; - start_response:一个发送HTTP响应的函数。
在application函数中调用:start_response('200 OK', [('Content-Type', 'text/html')])
就发送了HTTP响应的Header,注意Header只能发送一次,也就是这个函数只能被调用一次。start_response()
接收两个参数,一个是HTTP响应码,一个是一组list表示的HTTP的Header,每个Header用一个包含两个str
的tuple
表示。
通常情况下都应该把Content-Type
头发送给浏览器。其他很多常用的HTTP Header也应该发送。然后,函数的返回值b'<h1>Hello, web!</h1>'
将作为HTTP响应的Body发送给浏览器。
有了WSGI,我们关心的就是如何从environ
这个字典对象中拿到HTTP请求信息,然后构造HTML,通过start_response()
发送Header,最后返回Body。
整个application()
函数本身不涉及任何解析HTTP的部分,也就是说底层代码不需要我们编写,我们只负责在更高层次上考虑如何响应请求就可以了。
application()函数由WSGI服务器来调用,Python内置了一个简单的WSGI服务器,模块名为wsgiref
,这是一个纯Python编写的WSGI服务器的参考实现,完全符合WSGI的标准,但是不考虑运行效果,仅供开发和测试使用。
运行WSGI服务
hello.py:实现web应用程序的WSGI处理函数:
#!/usr/bin/python3
# coding=utf-8
def application(environ, start_response):
start_response('200 OK', [('Content-Type', 'text/html')])
return [b'<h1>喵呜嗷~这是一个HTML喔</h1>']
application()
server.py:负责启动WSGI服务器,加载application函数
#!/usr/bin/python3
# coding=utf-8
# 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()
上面两个文件必须放在同一个目录下,然后在命令行中输入python3 server.py
启动服务器:如果8000端口被占用可修改为其他端口。
服务器启动成功后打开浏览器输入http://localhost:8000/
或http://127.0.0.1:8000/
查看结果:
命令行可以看到wsgiref打印的log信息:
键盘上按control+C终止服务器
小结
无论多么复杂的web应用程序,入口都只是一个WSGI处理函数,HTTP请求的所有输入信息都可以通过environ
获得,HTTP响应的输出都可以通过start_response()
加上函数返回值作为Body。
web框架
在学习了WSGI框架之后,我们了解到web App就是写一个WSGI的处理函数,针对每个HTTP请求进行响应。
每一个URL可以对于GET和POST请求,以及PUT、DELETE等请求,我们只考虑最常见的GET和POST请求,处理的问题就是同时处理100个不同的URL。
URL到函数的映射可以交给web框架来做,Python中有上百个开源web框架,我们直接选用比较流行的flask来使用。
用pip进行 flask安装:
pip3 install flask
编写一个app.py
处理3个URL,分别是:
- GET /:首页,返回home;
- GET /sigin:登录页,显示登陆表单;
- POST /signin:处理登录页表单,显示登录结果
注意事项:同一个URL/signin
分别有GET和POST两种请求,映射到两个处理函数中。flask通过Python的装饰器在内部自动地把URL与函数关联起来。
#!/usr/bin/python3
# coding=utf-8
from flask import Flask
from flask import request
app = Flask(__name__)
@app.route('/', methods=['GET', 'POST'])
def home():
return '<h1>Home Page</h1>'
@app.route('/signin', methods=['GET'])
def signin_form():
return '''<form action="/signin" method="post">
<p><input name="username"></p>
<p><input name="password" type="password"></p>
<p><button type="submit">Sign In</button></p>
</form>'''
@app.route('/signin', methods=['POST'])
def signin():
# 需要从request对象读取表单内容:
if request.form['username']=='admin' and request.form['password']=='password':
return '<h3>Hello, admin!</h3>'
return '<h3>Bad username or password.</h3>'
if __name__ == '__main__':
app.run()
在命令行运行程序
python3 app.py
- Serving Flask app “app” (lazy loading)
- Environment: production
WARNING: This is a development server. Do not use it in a production deployment.
Use a production WSGI server instead.- Debug mode: off
- Running on http://127.0.0.1:5000/ (Press CTRL+C to quit)
127.0.0.1 - - [06/Aug/2019 19:28:49] “GET / HTTP/1.1” 200 -
打开浏览器地址栏,输入http://127.0.0.1:5000/
显示首页:
再输入http://127.0.0.1:5000/ signin
,显示登录表单:
预设的用户名和密码为admin
和password
,登录成功:
如果输入其他用户名和密码则登录失败。
常见Python web框架
小结
在web框架下编写web应用时注意力就从WSGI处理函数转移到了URL和对应的处理函数上,编写web应用更加简单。在编写URL处理函数时,除了配置URL外,从HTTP请求拿到用户数据也是非常重要的,web框架提供了对应了API来实现功能。
使用模板
web框架把我们从WSGI中拯救出来,我们只需要不断编写函数带上URL就可以继续webAPP的开发了。但是webAPP不仅仅是处理逻辑,页面展示也非常重要。因为HTML的复杂,所以,展示给用户的HTML页面不仅要正确而且要整洁美观,由于在Python代码里拼接字符串不大现实,所以模块技术应运而生。
使用模板,首先准备一个嵌入了一些变量和指令的HTML文档,然后根据我们传入的数据进行替换,得到最终的发送给用户的HTML文档:
这就是MVC模型:Model-View-Controller,模型-视图-控制器。
Python处理URL的函数就是C:controller,控制器,controller负责业务逻辑,比如检查用户名是否存在,取出用户信息等;
包含变量{{name}}
的模板就是V:view,视图,负责显示逻辑,通过简单地替换一些变量,view最终输出的就是用户看到的HTML;
model可以理解为存放和传递数据的容器,,view在替换变量的时候就是从model中取出相应的数据。
在上面的例子中,model就是一个dict
:{'name:':'Michael'}
。
Python支持关键字参数,所以很多web框架允许传入关键字参数,然后在框架内组装出一个dict
作为model。
接下来我们将直接输出字符串作为HTML的例子用MVC模式改写:
app.py:
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通过render_template()
函数来实现模板的渲染。和web框架类似,Python的模板也有很多,flask默认支持的模板是jinja2:
pip3 install jinja2
接下来的三个文件需要放入同一个文件夹内,并且该文件夹和app.py在统同级文件夹中。
home.html:用于显示首页的模板
<html>
<head>
<title>Home</title>
</head>
<body>
<h1 style="font-style:italic">Home Page</h1>
</body>
</html>
form.html:显示登陆表单的模板
<html>
<head>
<title>Please Sign In</title>
</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 In</button></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
重用为登录失败的模板。
启动app.py,在浏览器中查看模板的页面效果。
通过MVC模型我们在Python代码中处理Model和Controller,而View通过模板处理,这样我们就将Python代码和HTML代码最大限度地分离了。
使用模板的最大好处是模板改起来很方便,而且改完刷新浏览器即可看到效果,这对于调试HTML、CSS和JavaScript等工作而言非常重要。
在jinja2模板中,用{{name}}
表示一个需要替换的变量。如果需要循环、条件判断等指令语句,则用{% ... %}
表示指令。
比如循环输出页码:
{% for i in page_list %}
<a href="/page{{ i }}">{{ i }}</a>
{% endfor %}
如果page_list
是一个list:[1,2,3,4,5],则上述模板将输出5个超链接。
常用模板:
小结
有了MVC,我们可以将Python代码和HTML代码分离,HTML放入模板可以提升代码编写效率。
总结
虽然感觉没什么收获,但还是很开心呢。
推荐文档
推荐阅读w3school-html、w3school-css等文档。