总结一下HTTP请求的流程:
步骤1:浏览器首先向服务器发送HTTP请求,请求包括:
方法:GET
还是POST
,GET
仅请求资源,POST
会附带用户数据;
路径:/full_url_path
, /
表示首页;
域名:由Host头指定:Host: www.sina.com.cn
以及其他相关的Header;
如果是POST,那么请求还包括一个Body,包含用户数据。
步骤2:服务器向浏览器返回HTTP响应,响应包括:
响应代码:200
表示成功,3xx
表示重定向,4xx
表示客户端发送的请求有错误(404 not found 网页不存在),5xx
表示服务器端处理时发生了错误(500 internal server error 服务器内部错误);
响应类型:由Content-Type
指定,例如:Content-Type: text/html;charset=utf-8
表示响应类型是HTML文本,并且编码是UTF-8
,Content-Type: image/jpeg
表示响应类型是JPEG格式的图片;
以及其他相关的Header;
通常服务器的HTTP响应会携带内容,也就是有一个Body,包含响应的内容,网页的HTML源码就在Body中。
步骤3:如果浏览器还需要继续向服务器请求其他资源,比如图片,就再次发出HTTP请求,重复步骤1、2。
http协议分成两个大的部分,一个是请求,一个是相应。无论是请求还是相应都包含两个部分,一个是header,另外一个是body。(body是可选 的)
HTTP GET请求的格式:
GET /path HTTP/1.1
Header1: Value1
Header2: Value2
Header3: Value3
HTTP POST请求的格式:
注意:当遇到连续两个\r\n时,Header部分结束,后面的数据全部是Body。
POST /path HTTP/1.1
Header1: Value1
Header2: Value2
Header3: Value3
body data goes here...
HTTP响应的格式:
再次注意:HTTP响应如果包含body,也是通过\r\n\r\n来分隔的。
200 OK
Header1: Value1
Header2: Value2
Header3: Value3
body data goes here...
请再次注意,Body的数据类型由Content-Type头来确定,如果是网页,Body就是文本,如果是图片,Body就是图片的二进制数据。
当存在Content-Encoding
时,Body数据是被压缩的(gzip), 下解压缩才能有数据。
对于http 请求s.send(b'GET / HTTP/1.1\r\nHost: www.sina.com.cn\r\nConnection: close\r\n\r\n')
状态代码有三位数字组成,第一个数字定义了响应的类别,共分五种类别:
1xx:指示信息–表示请求已接收,继续处理
2xx:成功–表示请求已被成功接收、理解、接受
3xx:重定向–要完成请求必须进行更进一步的操作
4xx:客户端错误–请求有语法错误或请求无法实现
5xx:服务器端错误–服务器未能实现合法的请求
200 OK //客户端请求成功
400 Bad Request //客户端请求有语法错误,不能被服务器所理解
401 Unauthorized //请求未经授权,这个状态代码必须和WWW-Authenticate报头域一起使用
403 Forbidden //服务器收到请求,但是拒绝提供服务
404 Not Found //请求资源不存在,eg:输入了错误的URL
500 Internal Server Error //服务器发生不可预期的错误
503 Server Unavailable //服务器当前不能处理客户端的请求,一段时间后可能恢复正常
NOTE1: 关于HTTP的request请求,参考【HTTP】超简洁的实例 ——关于HTTP协议分析_bandaoyu的博客-CSDN博客_http例程
NOTE2: Book, HTTP权威指南 + 图解HTTP
·
HTML协议
HTML文档就是一系列的Tag组成,最外层的Tag是。规范的HTML也包含
...
和...
,由于HTML是富文档模型,还有一系列的Tag用来表示链接、图片、表格、表单等等。
CSS是Cascading Style Sheets(层叠样式表)的简称,CSS用来控制HTML里的所有元素如何展现
<html>
<head>
<title>Hellotitle>
<style>
h1 {
color: #333333;
font-size: 48px;
text-shadow: 3px 3px 3px #666666;
}
style>
head>
<body>
<h1>Hello, world!h1>
body>
html>
JavaScript - 和java没关系, 增加HTML的交互性,可以内嵌或者外部链接到HTML, 脚本语言,不需要编译
<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, The Shadow!h1>
body>
html>
熟练学习HTML、CSS和JavaScript是必须的:
http://www.w3schools.com/
以及一个对应的中文版本:
http://www.w3school.com.cn/
一个Web应用的本质就是:
静态服务器 Apache、Nginx、Lighttpd就是做上面的事, 但是如果要生成动态HTML,上面的所有步骤自己实现很麻烦,所有关于HTTP请求,解析,发送响应需要用专门的服务器软件实现, 一个统一接口WSGI:Web Server Gateway Interface解决了这个问题。
WSGI,也可称作Python Web Server Gateway Interface,开始于2003年,为Python语言定义Web服务器和服务器端程序的通用接口规范。WSGI的接口分为两个:一个是与Web服务器的接口,一个是与服务器端程序的接口;WSGI Server与Web服务器的接口包括uwsgi、fast cgi等。
python内置一个的WSGI服务器的参考实现(完全符合WSGI标准,但是不考虑任何运行效率,仅供开发和测试使用),这个模块叫wsgiref.
我们先编写hello.py
,实现Web应用程序的WSGI处理函数:
# hello.py
def application(environ, start_response):
start_response('200 OK', [('Content-Type', 'text/html')]) # 发送response header
body = 'Hello, %s!
' % (environ['PATH_INFO'][1:] or 'web') # 提取request中的PATH_INFO信息并传给body,[1:]表示读取第二个字符后面的,因为第一个字符永远是'/'
return [body.encode('utf-8')] # 返回body(html)
然后,再编写一个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请求, 需要手动停掉的Ctrl+C
httpd.serve_forever()
application()
函数就是符合WSGI标准的一个HTTP处理函数,它接收两个参数:
dict
对象;return
作为HTTP响应的Body发送给浏览器可以看出整个application()
函数本身没有涉及到任何解析HTTP的部分,我们只需考虑如何响应请求即可。
application()
函数必须由WSGI服务器来调用,如上面的wsgiref.
效果:
总结: 无论多么复杂的Web应用程序,入口都是一个WSGI处理函数。HTTP请求的所有输入信息都可以通过environ
获得,HTTP响应的输出都可以通过start_response()
加上函数返回值作为Body。
但是复杂的Web应用程序,光靠一个WSGI函数来处理还是太底层了,需要再抽象出Web框架,进一步简化Web开发。
WSGI可以针对每个HTTP请求写出一个函数,但是处理不同的URL(GET/POST/PUT/DELETE)就会很繁琐,而且维护性差。
Web框架是在WSGI接口之上进一步抽象,让我们专注于用一个函数处理一个URL,至于URL到函数的映射,交给框架做。
常见的Python Web框架还有:
Flask通过Python的装饰器在内部自动地把URL和函数给关联起来。
eg:
写一个app.py,处理3个URL,分别是:
GET /:首页,返回Home;
GET /signin:登录页,显示登录表单;
POST /signin:处理登录表单,显示登录结果。
NOTE: 实际的Web App应该拿到用户名和口令后,去数据库查询再比对,来判断用户是否能登录成功。
#!/usr/bin/env python3
# -*- coding: UTF-8 -*-
from flask import Flask
from flask import request
app = Flask(__name__)
@app.route('/', methods=['GET', 'POST']) # GET /:首页
def home():
return 'Welcome back Home
'
@app.route('/signin', methods=['GET']) # GET /signin:登录页
def signin_form():
return ''''''
@app.route('/signin', methods=['POST']) # POST /signin:处理登录表单
def signin():
if request.form['username'] == "admin" and request.form['password'] == "password":
return "Hello admin!
"
else:
return "Bad username or password!
"
if __name__ == "__main__":
app.run(debug=True) # 开启debug模式可以方便差错,production环境不开
模板承包了webapp的HTML的展示,使得我们不用手打这么多HTML代码,使用模板,我们需要预先准备一个HTML文档,里面嵌入了一些变量和指令,然后,根据我们传入的数据,替换后,得到最终的HTML,发送给用户。
这种模式就是Model-View-Controller,中文名“模型-视图-控制器”。
常用的是jinja2模板,用{{ name }}
表示一个需要替换的变量。用{% ... %}
表示指令循环、条件判断等指令语句。比如循环输出页码:
{% for i in page_list %}
{{ i }}
{% endfor %}
>>> 如果`page_list`是一个list:`[1, 2, 3, 4, 5]`,上面的模板将输出5个超链接。
除了Jinja2,常见的模板还有:
<% ... %>
和${xxx}
的一个模板;<% ... %>
和${xxx}
的一个模板;{% ... %}
和{{ xxx }}
的模板。下面使用jinja2模板改写例子:
## app.py ##
#!/usr/bin/env python3
# -*- coding: UTF-8 -*-
from flask import Flask
from flask import request
from flask import render_template
app = Flask(__name__)
@app.route('/', methods=['GET', 'POST'])
def home():
return render_template('home.html') # Flask通过render_template()函数来实现模板的渲染。
@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.html', username = username)
return render_template('form.html', message='Bad username or password!', username = username)
if __name__ == "__main__":
app.run(debug=True)
编辑模板如下:
## 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 %} # 通过是否检测到message来显示username/passwor错误的情况
<p style="color:red">{{message}}p>
{% endif %}
<form action="/signin" method="post">
<p><input name="username" placeholder="Username" value="{{username}}">p>
<p><input name="password" placeholder="Password" type="password">p>
<p><button type="submit">Sing Inbutton>p>
form>
body>
html>
## signin.html ##
<html>
<head>
<title>Welcome, {{username}}title>
head>
<body>
<p>Welcome, {{username}}!p>
body>
html>
NOTE: 一定要把模板放到正确的templates
目录下,templates
和app.py
在同级目录下