Skip to content

处理 HTTP 请求

URL 是一个请求的起源。当我们输入指向服务器所在地址的 URL ,都会向服务器发送一个 HTTP 请求。一个标准的 URL 由很多部分组成,以下面这个 URL 为例:

txt
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 访问本地示例程序的示例。

图2-3 在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

属性
pathu'query'
fullpathu'query?q=python'
hostu'127.0.0.1'
host_urlu'http://127.0.0.1/'
baseurlu'http://127.0.0.1/query'
urlu'http://127.0.0.1/hello?q=python'
urlrootu'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 中的查询字符串

python
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。注意,不要在生产环境中使用,这里只是为了图方便。

html
<link href="//unpkg.com/layui@2.9.1/dist/css/layui.css" rel="stylesheet">

使用 JavaScript 的时候也可以引入 cdn

html
<script src="//unpkg.com/layui@2.9.1/dist/layui.js"></script>
html

<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>
html

<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 的方式获取数据

html

<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 获取到上传的文件

html

<button type="button" class="layui-btn demo-class-accept" lay-options="{accept: 'file'}">
    <i class="layui-icon layui-icon-upload"></i>
    上传文件
</button>
html

<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,可以直接将其保存。

python
@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 请求:

python
# 限制请求方法  
@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 方法的请求。