16. Web编程

Web开发

随着互联网的兴起,人们发现CS架构不适合Web,最大的原因是Web应用程序的修改和升级非常迅速,而CS架构需要每个客户端逐个升级桌面APP,因此BS架构开始流行。

在BS架构下,客户端只需浏览器,应用程序的逻辑和数据都存储在服务器端。浏览器只需要请求服务器获取Web页面,并把Web页面展示给用户即可。Web页面也具有极强的交互性。由于Web页面使用HTML编写的,而HTML具备超强的表现力,并且服务端升级后,客户端无需任何部署就可以使用到最新的版本,因此BS架构迅速流行起来。

Web开发的阶段:
1、静态Web页面
2、CGI:Common Gateway Interface
3、ASP/JSP/PHP:脚本语言开发与HTML结合紧密。
4、MVC:Model-View-Controller的模式,ASP发展为ASP.Net,JSP和PHP也有一大堆MVC框架。

Python是一种解释型脚本语言,开发效率高,所以很适合来做WEB开发。Python有上百种Web开发框架,有很多成熟的模板技术。

HTTP协议简介

在Web应用中,服务器把网页传给浏览器,实际上就是把网页的HTML代码发送给浏览器,让浏览器显示出来。而浏览器和服务器之间的传输协议就是HTTP,所以:

HTML是一种用来定义网页的文本,会HTML,就可以编写网页
HTTP是在网络上传输HTML的协议,用于浏览器和服务器的通信。

chrome浏览器视图,开发者,开发者工具,就可以显示开发者工具:
Alt text

Elements显示网页的结构,Network显示浏览器和服务器的通信。我们点Network,确保第一个小红灯亮着,Chrome就会记录所有浏览器和服务器之间的通信:
Alt text

当我们在地址栏输入www.sina.com.cn时,浏览器将显示新浪的首页。在Network中,定位到第一条记录,点击,右侧将显示Request Headers,点击右侧的view source,我们就可以看到浏览器发给新浪服务器的请求了:
Alt text

最主要的头两行分析如下,第一行为

GET / HTTP/1.1

GET表示一个读取请求,将从服务器获得网页数据,/表示URL的路径,URL总是以/开头,/就表示首页,最后的HTTP/1.1表示采用的HTTP版本是1.1。目前HTTP协议的版本就是1.1,但是大部分服务器也支持1.0版本,主要区别在于1.1版本允许多个HTTP请求复用一个TCP连接,以加快传输速度。

第二行开始,每一行都类似于Xxx:abcdefg

Host: www.sina.com.cn

表示请求的域名是www.sina.com.cn,如果一天服务器有多个网站,服务器就需要通过Host来区分浏览器请求的是哪个网站。

继续往下找到Response Headers,点击view source,显示服务器返回的原始相应数据:
Alt text

HTTP相应分为Header和Body两部分,我们在Network中看到的Header最重要的几行如下:

200 OK

200表示一个成功的响应,后面的OK是说明。失败的响应有404 Not Found500Internal Server Error等。

Content-Type:text/html

Content-Type指示响应的内容,这里是text/html表示HTML网页。请注意浏览器就是靠Content-Type来判断响应的内容是网页还是图片,是时频还是音乐,不是靠URL来判断响应的内容的。

HTTP响应的Body就是HTML源码,我们可以直接查看:
Alt text

当浏览器读取到新浪首页的HTML源码后,它会解析HTML,显示页面,然后根据HTML里面的各种链接,再发送HTTP请求到新浪服务器,拿到相应的图片、时频、Flash。JavaScript脚本、CSS等各种资源,最终显示出一个完整的页面。我们在Network下回看到很多额外的请求。

HTTP请求

1、浏览器首先向服务器发送HTTP请求,请求包括:

方法:GET–仅请求资源、POST–附带用户数据
路径:由Host头指定–Host:www.sina.com.cn
以及其他相关的Header

2、服务器向浏览器返回HTTP相应,相应包括:

响应代码:200表示成功,3xx表示重定向,4xx表示客户端发送的请求有错误,5xx表示服务器端处理时发生了错误;
响应类型:由Content-Type指定;
以及其他相关的Header;
通常服务器的HTTP响应会携带内容,也就是有一个Body,包含响应的内容,网页的HTML源码就在Body中。

3、如果浏览器还需要继续向服务器请求其他资源,比如图片,就再次发出HTTP请求,重复步骤1、2。

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请求的格式:

1
2
3
4
GET /path HTTP/1.1
Header1: Value1
Header2: Value2
Header3: Value3

每个Header一行一个,换行符是\r\n。HTTP POST请求的格式是:

1
2
3
4
5
6
POST /path HTTP/1.1
Header1: Value1
Header2: Value2
Header3: Value3
body data goes here...

当遇到连续两个\r\n时,Header部分结束,后面的数据都是Body。

HTTP相应的格式:

1
2
3
4
5
6
200 OK
Header1: Value1
Header2: Value2
Header3: Value3
body data goes here...

HTTP响应如果包含body,也是通过\r\n\r\n来分割的。Body数据类型是由Content-Type来确定的。


HTML简介

HTML定义了一套语法规则,来告诉浏览器如何把一个丰富多彩的页面显示出来。最简单的HTML长这样:

1
2
3
4
5
6
7
8
<html>
<head>
<title>Hello</title>
</head>
<body>
<h1>Hello, world!</h1>
</body>
</html>

可以用文本编辑器编写HTML,然后保存为hello.html,双击或者把文件拖到浏览器中,就可以看到效果:
Alt text
HTML文档就是一些列的Tag组成的,最外层的Tag是<html>。规范的HTML也包含<head>...</head><body>...<body>注意不要和HTTP中的Header和Body搞混了。由于HTML是富文档模型,所以还有一系列的Tag来表示链接、图片、表格、表单等等。

CSS简介

CSS是Cascading Style Sheets(层叠样式表),CSS用来控制HTML里的所有元素如何展现,比如,给标题元素<h1>加一个样式,变成48号字体,灰色,带阴影。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
<html>
<head>
<title>Hello</title>
<style>
h1 {
color: #333333;
font-size: 48px;
text-shadow: 3px 3px 3px #666666;
}
</style>
</head>
<body>
<h1>Hello, world!</h1>
</body>
</html>

效果如下:
Alt text

JavaScript简介

JavaScript和Java没有关系。JavaScript是为了让HTML具有交互性而作为脚本语言添加的,JavaScript既可以内嵌到HTML中,也可以从外部链接到HTML中。如果我们希望当用户点击标题时把标题变为红色,就必须通过JavaScript来实现:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
<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>

Alt text

如果要学习Web开发,首先要对HTML、CSS和JavaScript作一定的了解。HTML定义了页面的内容,CSS来控制页面元素的样式,而JavaScript负责页面的交互逻辑。


WSGI接口

了解HTTP协议和HTML文档,一个Web应用的本质是:

浏览器发送一个请求—>服务器收到请求,生成一个HTML文档—>服务器把HTML文档作为HTTP相应的Body发送给浏览器—>浏览器收到HTTP响应,从HTTP Body中取出HTML并显示。

最简单的WEB应用就是先把HTML用文件保存好,用一个线程的HTTP服务器软件接收用户请求,从文件中读取HTML,返回。Apache、Nginx、Lighttpd等这些常见的静态服务器就是做这件事情的。

如果要动态生成HTML,就需要把上述步骤自己实现。不过那些工作量很大,所以底层的代码由专门的服务器软件实现,我们用python专注于生成HTML文档。我们不希望接触到TCP连接。HTTP原始请求和响应格式,所以需要一个统一的接口,让我们专心用Python编写Web业务。

这个接口就是WSGI:Web Server Gateway Interface,只要求开发者实现一个函数,就可以相应HTTP请求。最简单的实例如下:

1
2
3
4
>>> def application(envision, start_response):
... start_response('200 OK',[('Content-Type', 'text/html')])
... return [b'<h1>Hello, world!</h1>']
...

上面的application()函数就是符合WSGI标准的一个HTTP处理函数,它接收两个参数:

environ:一个包含所有HTTP请求信息的dict对象
start_response:一个发送HTTP响应的函数。

application()函数中,调用:

start_response(‘200 OK’,[(‘Content-Type’, ‘text/html’)])

就发送了HTTP响应的Header,注意Header只能发送一次,也就是只能调用一次start_response()函数。start_response()函数接收两个参数,一个是HTTP响应码,一个是一组list表示的HTTP Header,每个Header用一个包含两个strtuple表示。

通常情况下,都应该把Connect-Type头发送给浏览器。其他很多常用的HTTP Header也应该发送。

然后函数的返回值b'<h1>Hello, world!</h1>'将作为HTTP响应的Body发送给浏览器。

有了WSGI,我们关心的就是如何从environ这个dict对象中拿到HTTP请求信息然后构造HTML,通过start_response()函数发送Header,最后返回Body。

整个application()函数本身并没有涉及到任何解析HTTP的部分,也就是说,底层代码不需要我们自己编写,我们只负责在更高层次上考虑如何响应请求就可以了。并且application()必须由WSGI服务器来调用。有很多符合WSGI规范的服务器,我们可以挑选一个来用。Python内置了一个WSGI服务器,这个模块叫wsgiref,它是用纯Python编写的WSGI服务器的参考实现。

运行WSGI服务

我们先编写hello.py,实现WEB应用程序的WSGI处理函数:

1
2
3
def application(environ, start_response):
start_response('200 OK',[('Content-Type','text/html')])
return [b'<h1>Hello, world!</h1>']

然后再编写一个server.py,负责启动WSGI服务器,加载application()函数:

1
2
3
4
5
6
7
8
9
from wsgiref.simple_server import make_server
from hello import application
#创建一个服务器,IP地址为空,端口为8000,处理函数是application
httpd = make_server('', 8000, application)
print('Serving HTTP on port 8000...')
httpd.serve_forever()

两个文件在一个目录下,执行服务器程序:

1
2
3
4
D:\笔记\Python\Notepad++>python server.py
Serving HTTP on port 8000...
127.0.0.1 - - [10/Nov/2016 20:47:17] "GET / HTTP/1.1" 200 22
127.0.0.1 - - [10/Nov/2016 20:47:17] "GET /favicon.ico HTTP/1.1" 200 22

启动成功后,打开浏览器,输入http://localhost:8000/就可以看到结果了。
Alt text

Ctrl+c终止服务器。

为了丰富WEB内容,可以在environ里读取PATH_INFO,这样可以显示更加动态的内容:

1
2
3
4
def application(environ, start_response):
start_response('200 OK',[('Content-Type','text/html')])
body='<h1>Hello, %s!</h1>' % (environ['PATH_INFO'][1:] or 'web')
return [body.encode('utf-8')]

你可以在地址栏输入用户名作为URL的一部分,将返回Hello,xx!
Alt text

无论多么复杂的WEB应用程序,入口都是一个WSGI处理函数。HTTP请求的所有输入信息都可以通过environ获得,HTTP响应的输出都可以通过start_response()加上函数返回值作为Body。复杂的Web应用程序,光靠一个WSGI函数来处理还是太底层了,我们需要在WSGI之上再抽象出Web框架,进一步简化开发。


使用Web框架

了解了WSGI框架,我们发现:其实一个WebAPP就是写一个WSGI的处理函数,针对每个HTTP请求进行相应。

但是如何处理HTTP请求不是问题,问题是如何处理100个不同的URL。

每一个URL可以对应GET和POST请求,当然还有PUT、DELETE等请求,但是通常我们只考虑最常见的GET和POST请求。

一个最简单的想法是从environ变量里取出HTTP请求的信息,然后逐个判断:

1
2
3
4
5
6
7
8
def application(environ, start_response):
method = environ['REQUEST_METHOD']
path = environ['PATH_INFO']
if method=='GET' and path=='/':
return handle_home(environ, start_response)
if method=='POST' and path='/signin':
return handle_signin(environ, start_response)
...

这样写下去代码是没法维护的,因为WSGI提供的接口虽然比HTTP接口高级了不少,但是和Web APP的处理逻辑相比,还是比较低级的,我们需要在WSGI接口之上能进一步抽象,让我们专注于用一个函数处理一个URL,至于URL到函数 的映射就交给WEB框架来做。

Python提供了上百个开源的框架,最流行的为flask

写一个app.py,处理三个URL,分别是:

1、GET /:首页,返回Home
2、GET /signin:登录页,显示登录表单
3、POST /signin:处理登录表单,显示登录结果。

同一个URL/signin分别有GET和POST两种请求,映射到两个处理函数争取。Flask通过Python的装饰器在内部自动地把URL和函数关联起来:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
from flask import Flask
from flask import request
app=Flask(__name__)
@app.route('/',methods=['GET', 'Post'])
def home():
return '<h1>Home</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</p>
</form>'''
@app.route('/signin', methods=['POST'])
def signin():
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()

运行python app.py,Flask自带的Server在端口5000上监听:

1
2
3
4
D:\笔记\Python\Notepad++>python app.py
* Running on http://127.0.0.1:5000/ (Press CTRL+C to quit)
127.0.0.1 - - [11/Nov/2016 18:07:12] "GET / HTTP/1.1" 200 -
127.0.0.1 - - [11/Nov/2016 18:07:12] "GET /favicon.ico HTTP/1.1" 404 -

在浏览器首页输入:http://localhost:5000/:
Alt text

首页显示正确,地址栏输入:http://localhost:5000/signin:
Alt text

输入预设的用户名和口令:
Alt text
Alt text

实际的Web APP应该拿到用户名和口令后,去数据库查询再对比,来判断用户是否能登录成功。除了Flask,常见的python框架还有:Django、web.py、Bottle、Tornado

有了Web框架,我们在编写Web应用时,注意力就从WSGI处理函数转移到URL+对应的处理函数,编写变得简单。在编写URL处理函数时,除了配置URL外,从HTTP请求拿到用户数据也是非常重要的。Web框架都提供了自己的API来实现这些功能。Flask通过request.form['name']来获取表单的内容。


使用模板

Web框架把我们从WSGI中拯救出来了,我们只需要不断地编写函数,带上URL,就可以继续Web APP的开发了。Web App最复杂的部分在HTML页面。HTML不仅要正确,还要通过CSS美化,再加上复杂的JavaScript脚本来实现各种交互和动画效果。用python的字符串是无法实现的。所以出现了模板技术。

使用模板首先需要预先准备一个HTML文档,这个HTMl文档不是普通的HTML,而是嵌入了一些变量和指令,然后根据我们传入的数据,替换后得到最终的HTMl,发送给用户:
Alt text

这就是传说中的MVC:Model-View-Controller,模型-视图-控制器

Python处理URL的函数就是C:Controller,Controller负责业务逻辑,比如检查用户名是否存在,取出用户信息等等。包含变量的模板就是V:View,View负责显示逻辑,通过简单地替换一些变量,View最终输出的就是用户看到的HTML。

上面的例子中,Model就是一个dict

{ ‘name’: ‘Michael’}

因为Python支持关键字参数,很多Web框架允许传入关键字参数,然后在框架内部组装出一个dict作为Model。

对之前的app.py进行改写:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
from flask import Flask
from flask import 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,用jinja2来编写模板home.html、显示首页:

1
2
3
4
5
6
7
8
<html>
<head>
<title>Home</title>
</head>
<body>
<h1 style="font-style:italic">Home</h1>
</body>
</html

form.html:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
<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:

1
2
3
4
5
6
7
8
<html>
<head>
<title>Welcome, {{ username }}</title>
</head>
<body>
<p>Welcome, {{ username }}!</p>
</body>
</html>

Donate comment here