处理 HTTP 请求
URL 是一个请求的起源。当我们输入指向服务器所在地址的 URL ,都会向服务器发送一个 HTTP 请求。一个标准的 URL 由很多部分组成,以下面这个 URL 为例:
https://zhengxinonly.com/query?q=python
这个 UR L的各个组成部分:
信 息 | 说 明 |
---|---|
https:// | 协议字符串,指定要使用的协议 |
zhengxinonly.com | 服务器的地址(域名) |
/query | 要获取的资源路径(path),类似UNIX的文件目录结构 |
?q=python | 查询字符串(query string) |
URL 中的查询字符串用来向指定的资源传递参数。查询字符串从问号 ?
开始,以键值对的形式写出,多个键值对之间使用 &
分隔。
请求报文
当我们在浏览器中访问这个 URL 时,随之产生的是一个发向 https://zhengxinonly.com/
所在服务器的请求。请求的实质是发送到服务器的一些数据,这种浏览器与服务器之间交互的数据被称为报文 (message),请求时浏览器发送的数据被称为请求报文(request message),而服务器返回的数据被称为响应报文(response message)。
请求报文由请求的方法、URL、协议版本、首部字段(header)以及内容实体组成。
请求报文示意表
组成说明 | 请求报文内容 |
---|---|
报文首部:请求行(方法、URL、协议) | GET /hello HTTP/1.1 |
报文首部:各种首部字段 | Host: 127.0.0.1:5000 Connection: keep-alive Cache-Control: max-age=0 User-Agent: Mozilla/5.0 (Windows NT 6.1; Win64; x64)AppleWebKit/537.36 (KHTML, like Gecko) Chrome/59.0.3071.104 Safari/537.36 ... |
空行 | |
报文主体 | name='zhengsan' |
下图是使用 Chrome 访问本地示例程序的示例。
HTTP 通过方法来区分不同的请求类型。比如,当你直接访问一个页面时,请求的方法是 GET ;当你在某个页面填写了表单并提交时,请求方法则通常为 POST。下表是常见的几种 HTTP 方法类型
常见的 HTTP 方法
方 法 | 说 明 |
---|---|
GET | 获取资源 |
POST | 传输数据 |
PUT | 传输文件 |
DELETE | 删除资源 |
HEAD | 获得报文首部 |
OPTIONS | 询问支持的方法 |
报文首部包含了请求的各种信息和设置,比如客户端的类型、是否设置缓存、语言偏好等。
如果运行了示例程序,那么当你在浏览器中访问 http://127.0.0.1:5000 时,开发服务器会在命令行中输出一条记录日志,其中包含请求的主要信息:
127.0.0.1 - - [03/Jul/2019 16:33:30] "GET / HTTP/1.1" 200 -
Request 对象
请求解析和响应封装实际上大部分是由 Werkzeug 完成的,Flask 子类化 Werkzeug 的请求(Request)和响应(Response)对象并添加了和程序相关的特定功能。
假设请求的 URL 是 http://127.0.0.1/query?q=python
,当 Flask 接收到请求后,请求对象 会提供多个属性来获取 URL 的各个部分,常用的属性如下表所示。
使用 request 的属性获取请求 URL
属性 | 值 |
---|---|
path | u'query' |
fullpath | u'query?q=python' |
host | u'127.0.0.1' |
host_url | u'http://127.0.0.1/' |
baseurl | u'http://127.0.0.1/query' |
url | u'http://127.0.0.1/hello?q=python' |
urlroot | u'http://127.0.0.1/' |
除了 URL,请求报文中的其他信息都可以通过 request 对象提供的属性和方法获取。
属性 | 说明 | 类型 |
---|---|---|
data | 记录请求的数据,并转换为字符串 | * |
form | 记录请求中的表单数据 | MultiDict |
args | 记录请求中的查询参数 | MultiDict |
cookies | 记录请求中的 cookie 信息 | Dict |
headers | 记录请求中的报文头 | EnvironHeaders |
method | 记录请求使用的HTTP方法 | GET/POST |
url | 记录请求的URL地址 | string |
files | 记录请求上传的文件 | * |
json | 记录请求中的 json 数据 | Dict |
通用属性
请求对象的属性有很多,但是有些属性在特定的请求之下才能获取到数据。
示例:获取请求 URL 中的查询字符串
from flask import Flask, request
app = Flask(__name__)
# get /request?page=1&limit=10
@app.route('/request') # Request 对象
def request():
# request 请求体对象,浏览器传递过来的
print('请求对象拥有的方法:\t', dir(request))
print('请求方法:\t', request.method)
print('请求头:\t', request.headers)
print('请求地址:\t', request.url)
print('请求参数:\t', request.args)
return render_template('index.html')
表单属性
当前端 form 表单默认提交,或者是 ajax 使用 contentType:'application/x-www-form-urlencoded'
时,服务器需要通过 request.form
form 表单默认提交
在最前面引入 layui cdn。注意,不要在生产环境中使用,这里只是为了图方便。
<link href="//unpkg.com/layui@2.9.1/dist/css/layui.css" rel="stylesheet">
使用 JavaScript 的时候也可以引入 cdn
<script src="//unpkg.com/layui@2.9.1/dist/layui.js"></script>
<form
method="post"
class="layui-form"
style="width: 320px;margin: 21px auto 0;"
>
<div class="demo-login-container">
<div class="layui-form-item">
<div class="layui-input-wrap">
<div class="layui-input-prefix">
<i class="layui-icon layui-icon-username"></i>
</div>
<input
type="text"
name="username"
value=""
lay-verify="required"
placeholder="用户名"
lay-reqtext="请填写用户名"
autocomplete="off"
class="layui-input"
lay-affix="clear"
/>
</div>
</div>
<div class="layui-form-item">
<div class="layui-input-wrap">
<div class="layui-input-prefix">
<i class="layui-icon layui-icon-password"></i>
</div>
<input
type="password"
name="password"
value=""
lay-verify="required"
placeholder="密 码"
lay-reqtext="请填写密码"
autocomplete="off"
class="layui-input"
lay-affix="eye"
/>
</div>
</div>
<div class="layui-form-item">
<button
class="layui-btn layui-btn-fluid"
lay-submit
lay-filter="demo-login"
>
登录
</button>
</div>
</div>
</form>
<script src="//unpkg.com/layui@2.9.1/dist/layui.js"></script>
<script>
layui.use(function () {
var form = layui.form;
var $ = layui.$;
form.on("submit(demo-login)", function (data) {
$.ajax({
url: "/login",
type: "POST",
data: data.field,
success: function (res) {
console.log(res);
},
});
return false;
});
});
</script>
接口请求属性
ajax 使用 contentType:'application/json'
时,服务器需要通过 request.json
的方式获取数据
<script>
layui.use(function () {
var form = layui.form;
var $ = layui.$;
form.on("submit(demo-login)", function (data) {
$.ajax({
url: "/login",
type: "POST",
contentType: "application/json",
data: JSON.stringify(data.field),
success: function (res) {
console.log(res);
},
});
return false;
});
});
</script>
files
上传文件时获取文件对象的属性
使用 form 表单默认提交文件域时,可以在服务器通过 request.files
获取到上传的文件
<button
type="button"
class="layui-btn demo-class-accept"
lay-options="{accept: 'file'}"
>
<i class="layui-icon layui-icon-upload"></i>
上传文件
</button>
<script>
layui.use(function () {
var upload = layui.upload;
var layer = layui.layer;
upload.render({
elem: ".demo-class-accept", // 绑定多个元素
url: "/file", // 此处配置你自己的上传接口即可
accept: "file", // 普通文件
done: function (res) {
layer.msg("上传成功");
console.log(res);
},
});
});
</script>
上传到服务的文件被接受之后是一个 FileStorage,可以直接将其保存。
@app.post('/file')
def file_post():
file = request.files.get('file')
file.save(file.filename)
return {'msg': 'ok'}
HTTP 方法
前面通过 flask routes 命令打印出的路由列表可以看到,每一个路由除了包含 URL 规则外,还设置了监听的 HTTP 方法。GET 是最常用的 HTTP 方法,所以视图函数默认监听的方法类型就是 GET,HEAD、 OPTIONS 方法的请求由 Flask 处理,而像 DELETE、PUT 等方法一般不会在 程序中实现,在后面我们构建 Web API 时才会用到这些方法。
我们可以在 app.route() 装饰器中使用 methods 参数传入一个包含监听 的 HTTP 方法的可迭代对象。比如,下面的视图函数同时监听 GET 请求和 POST 请求:
# 限制请求方法
@app.route('/login', methods=["GET", "POST"])
def login():
if request.method == 'GET':
# get 请求模板文件(html) post 提交模板文件输入的用户名与密码
print('username', request.args.get('username')) # 查询参数 ?username=正心&pwd=123456
return render_template('login.html')
elif request.method == 'POST':
print('浏览器提交的数据', request.json)
return {'message': '提交数据成功'}, 5555 # 自定义响应体
当某个请求的方法不符合要求时,请求将无法被正常处理。比如在提交表单时通常使用 POST 方法,而如果提交
的目标 URL 对应的视图函数只允许 GET 方法,这时 Flask 会自动返回一个 405 错误响应(Method Not Allowed,表示请求方法不允许)。
通过定义方法列表,我们可以为同一个 URL 规则定义多个视图函数, 分别处理不同 HTTP 方法的请求。