处理HTTP请求和响应

前一篇笔记我们介绍了Django中的路由系统,路由系统通过声明式的匹配机制能够将HTTP请求路由到视图函数。这篇笔记我们继续详细介绍视图函数(FBV),包括视图函数中如何获取HTTP请求参数和请求内容以及如何生成HTTP响应。

视图函数(FBV)和视图类(CBV)

Django中,定义视图逻辑代码的方式其实有两种:视图函数(FBV)视图类(CBV)。FBV简单直观且功能强大,是Django视图层的核心基础;而CBV则相对来说有一些争议,CBV是出于提高代码复用性而引入的,它设计的非常复杂和抽象,使用门槛高且可定制性差,实际开发中完全不使用CBV也是可行的,不过合理使用CBV确实可以起到简化代码提高代码复用性的目的。

这篇笔记我们仍以FBV为基础进行介绍,有关CBV的内容我们放到后续章节。

处理HTTP请求

我们知道HTTP协议是一种文本协议,不过Django中具体解析HTTP请求主要是由框架自动完成的,我们只需要在urls.py定义路由规则并在视图函数中编写取值的代码逻辑即可。Django能很方便的实现传统URL风格参数解析或RESTful的URL风格参数解析,下面我们以例子的形式简单介绍。

获取GET请求参数

下面是一个请求示例,请求中包含一个GET参数blogId

GET http://localhost:8000/demo01/blogs/?blogId=2

下面代码中,我们在视图函数里获取了GET请求参数并打印。

from django.shortcuts import render


def blogs(request):
    blog_id = request.GET.get('blogId')
    print('Blog ID ', type(blog_id), blog_id)
    return render(request, 'success.html')

GET参数是通过request.GET这个QueryDict对象获取的,我们可以通过get()方法获取,如果键存在则返回参数字符串值;如果不存在get()方法返回None。实际上,通过下标从QueryDict对象中获取参数也是可以的,写法例如blog_id = request.GET['blogId'],但不推荐这种方式,因为如果键不存在下标获取会直接抛出异常。

获取RESTful风格的路径参数

下面是一个RESTful风格的请求示例。

GET http://localhost:8000/demo01/blogs/2/

前面章节我们介绍过,这种风格的URL参数我们可以在路由配置中通过转换器匹配进行设置。

from django.urls import path
from demo01.views import blogs

urlpatterns = [
    path('blogs/<int:blog_id>/', blogs)
]

视图函数写法如下,我们直接以blog_id为参数名接收匹配到的值即可,Django会自动将匹配到的值作为参数传递给视图函数。

from django.shortcuts import render


def blogs(request, blog_id):
    print('Blog ID ', type(blog_id), blog_id)
    return render(request, 'success.html')

如果用户访问时没有传递blog_id参数,那么路由匹配就不会成功,未找到合适的路由服务端就会响应404错误。

获取请求头信息

请求头信息可以从request.META对象中获取,下面是一个例子。

from django.shortcuts import render


def blogs(request):
    user_agent = request.META.get('HTTP_USER_AGENT', 'unknown')
    print(user_agent)
    return render(request, 'success.html')

request.META对象包含了大量请求相关的基础信息,包括请求头、请求方法、协议、请求路径等,具体可以参考官方文档。我们如果需要获取请求头,可以通过HTTP_前缀加上请求头名称的方式获取,例如HTTP_USER_AGENT对应获取USER-AGENT请求头,注意HTTP协议规定请求头名称是大小写不敏感的。对于自定义请求头也是使用类似的转换规则,例如HTTP_X_MY_CUSTOM_HEADER将对应于X-MY-CUSTOM-HEADER请求头。如果想要获取所有请求头,打印request.META中以HTTP_为前缀的字段即可。

获取Cookie

Cookie信息可以从request.COOKIES对象中获取,下面是一个例子。

from django.http import HttpResponse


def index(request):
    username = request.COOKIES.get('username')
    print(username)
    return HttpResponse('OK')

代码中,我们获取了名为username的Cookie值,如果不存在则返回None

解析表单请求

表单格式实际上有form-datax-www-form-urlencoded两种格式,Django对这两种格式表单进行了统一的支持。

from django.http import HttpResponseNotAllowed
from django.shortcuts import render


def post_blog(request):
    if request.method == 'POST':
        title = request.POST.get('title')
        content = request.POST.get('content')
        print(title)
        print(content)
        return render(request, 'success.html')
    else:
        return HttpResponseNotAllowed(['POST'])

读取表单请求和读取GET参数类似,只需要通过request.POST对象调用get()方法获取请求参数值即可,如果参数不存在,get()方法返回None

注意:Django中默认配置启用了CSRF中间件,POST请求必须带上CSRF_TOKEN,上述代码如果直接在Postman等接口调试工具中请求其实是无法成功的,CSRF_TOKEN可以通过在表单模板中使用{% csrf_token %}获取,并随表单参数提交或使用X-CSRFToken请求头提交。有关CSRF_TOKEN的问题后文会详细介绍。

解析请求体JSON

如果使用请求体传递JSON数据(常见于现代的前后端分离工程),我们可以直接获取request.body并使用Python内置的json模块解析即可。

import json

from django.http import HttpResponseNotAllowed, HttpResponse


def post_blog(request):
    if request.method == 'POST':
        data = json.loads(request.body)
        print(data.get('title'))
        print(data.get('content'))
        return HttpResponse('OK')
    else:
        return HttpResponseNotAllowed(['POST'])

代码中,我们的视图函数解析如下的JSON数据结构。

{
    "title": "123",
    "content": "abc"
}

关于POST请求的CSRF参数问题

我们前面了解到,Django中默认配置启用了CSRF中间件,这要求POST请求必须带一个CSRF_TOKEN参数,实际上对于PUT、DELETE、PATCH请求也需要该参数,这个参数需要通过模板引擎获取,并通过POST表单键值对的形式提交回Django框架。下面例子代码演示了模板中如何获取CSRF_TOKEN

注:有关什么是CSRF攻击可以参考信息安全/Web安全/CSRF攻击相关章节。

<!doctype html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport"
          content="width=device-width, user-scalable=no, initial-scale=1.0, maximum-scale=1.0, minimum-scale=1.0">
    <meta http-equiv="X-UA-Compatible" content="ie=edge">
    <title>表单页</title>
</head>
<body>
<form method="post">
    {% csrf_token %}
    <label for="username">用户名</label>
    <input type="text" id="username" name="username">
    <input type="submit" value="Submit">
</form>
</body>
</html>

程序运行后,{% csrf_token %}会被替换为如下形式。此时提交表单,CSRF_TOKEN参数会被以csrfmiddlewaretoken作为名字随同表单提交到后端,CSRF中间件会检查该参数值,并决定是否处理请求。

<input type="hidden" name="csrfmiddlewaretoken" value="ae8DOdPL6IyIJFBv5kuG48HfhTabwe2XRZ1b9Fug6WmrWPFh6NpnvJiWnn5aJ2Nw">

对于前后端分离工程,我们提交POST请求时一般都采用JSON格式,且前后端分离工程可能完全不使用模板引擎。对于这类工程,一种可行的方式是编写专门的视图函数,每次提交请求前访问CSRF_TOKEN生成接口,由后端新生成一个CSRF_TOKEN。下面代码中,我们用Python代码的形式获取CSRF_TOKEN,然后通过AJAX方式将其传给前端。

from django.http import JsonResponse
from django.middleware import csrf


def get_token(request):
    csrf_token = csrf.get_token(request)
    return JsonResponse({
        'csrf_token': csrf_token
    })

在前端代码中,我们需要先通过AJAX请求获取CSRF_TOKEN,然后将其作为请求头X-CSRFToken的值提交给后端,建议每次POST请求前都先获取CSRF_TOKEN。下面例子使用JQuery编写附带X-CSRFToken的POST请求。

function postJson() {
    $.ajax({
        type: "POST",
        async: true,
        url: '/csrftest/post',
        headers: {
            'X-CSRFToken': csrf_token
        },
        data: JSON.stringify({
        'username': 'Tom',
        'password': 'abc123'
    }),
        dataType: "json",
        contentType: "application/json; charset=utf-8",
        success: function (msg) {
        },
        error: function (err) {
            alert('POST请求失败!');
        }
    });
}

此外,如果你真的确认不需要CSRF校验功能(常规工程中不推荐),可以在项目配置文件settings.py中禁用CSRF中间件。

MIDDLEWARE = [
    'django.middleware.security.SecurityMiddleware',
    'django.contrib.sessions.middleware.SessionMiddleware',
    'django.middleware.common.CommonMiddleware',
    # 'django.middleware.csrf.CsrfViewMiddleware',
    'django.contrib.auth.middleware.AuthenticationMiddleware',
    'django.contrib.messages.middleware.MessageMiddleware',
    'django.middleware.clickjacking.XFrameOptionsMiddleware',
]

返回HTTP响应

Django中,HTTP响应主要分为以下几种类型:

基本响应:可能是一个简单的字符串或是一个404状态。

模板响应:返回模板页面,用于传统的非前后端分离工程。

重定向响应:用于服务端返回重定向跳转。

JSON响应:返回JSON数据,用于现代前后端分离工程。

返回基本响应

下面代码向页面输出一个OK字符串,状态码为200

from django.http import HttpResponse


def index(request):
    return HttpResponse('OK')

返回错误状态的方法也差不多,下面代码向页面输出一个Http 400 Bad Request字符串,状态码为400

from django.http import HttpResponseBadRequest


def index(request):
    return HttpResponseBadRequest("Http 400 Bad Request")

其余常用的响应状态码,比如403404500等参考文档选择对应的响应封装函数即可。

响应模板页面

响应模板页面需要用到render()函数,下面是一个例子。

from django.shortcuts import render


def index(request):
    return render(request, 'index.html', {
        'msg': 'hello'
    })

其中,三个参数分别是request对象、模板文件(可以带文件夹路径)和模板包含的数据对象,模板引用数据方式如下。

<!doctype html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport"
          content="width=device-width, user-scalable=no, initial-scale=1.0, maximum-scale=1.0, minimum-scale=1.0">
    <meta http-equiv="X-UA-Compatible" content="ie=edge">
    <title>Document</title>
</head>
<body>
{{ msg }}
</body>
</html>

返回重定向

服务端重定向本质上就是返回301302响应,常用于非前后端分离项目,例如提交表单成功后,没什么信息可显示,需要重定向到另一个页面这种场景。

from django.http import HttpResponseRedirect


def index(request):
    return HttpResponseRedirect("/app/index")

HttpResponseRedirect()会返回302重定向响应,浏览器会根据响应信息中的Location响应头自动进行跳转到对应路径。

返回JSON数据

前后端分离项目中,我们经常需要返回JSON数据而非页面,Django对此也进行了封装,下面是一个例子。

from django.http import JsonResponse


def index(request):
    return JsonResponse({
        'username': 'tom',
        'age': 12
    })

其中,JsonResponse的参数就是返回的JSON内容,这个响应Django会自动帮我们加上Content-Type: application/json的响应头,告知浏览器该响应是JSON格式。

设置响应头

Django中可以通过对HttpResponse对象进行一些额外设置来添加响应头。

from django.http import HttpResponse


def index(request):
    response = HttpResponse('OK')
    response['X-MY-CUSTOM-HEADER'] = 'hello'
    return response

HttpResponse对象实现了运算符重载,我们可以直接通过类似操作字典的写法对其设置响应头。

设置Cookie

HttpResponseset_cookie()方法能够设置Cookie。

from django.http import HttpResponse


def index(request):
    response = HttpResponse('OK')
    response.set_cookie('username', 'tom', max_age=3600)
    return response

代码中,set_cookie()方法接收三个参数,分别是Cookie名称、Cookie值和Cookie过期时间(单位为秒)。设置Cookie时有以下几个常用的命名参数。

  • max_age:Cookie过期时间,不设置默认浏览器关闭后Cookie即失效。
  • path:Cookie生效的路径,默认为/
  • secure:安全设置,是否限制仅允许HTTPS传输Cookie,默认为False
  • httponly:安全设置,是否禁止JavaScript访问Cookie,默认为False
  • samesite:安全设置,Cookie的跨站设置,可以设置为LaxStrictNone,用以防止跨站请求伪造攻击(CSRF)。默认为None
  • domain:Cookie生效的域名,可以用于子域名之间共享Cookie。

调用delete_cookie()方法可以删除Cookie。

from django.http import HttpResponse


def index(request):
    response = HttpResponse('OK')
    response.delete_cookie('username')
    return response

删除Cookie的原理其实就是将Cookie的超时时间设置为0,即立即失效。

返回流式响应

流式响应(EventStream)是一种特殊的HTTP响应类型,它允许我们以流的形式返回数据,而不是一次性返回所有数据。流式响应广泛应用于需要实时更新数据的场景,例如动态展示股票交易信息等。下面例子实现了一个简单的text/event-stream格式的流式响应,它每秒向客户端发送一个数字。

import time

from django.http import StreamingHttpResponse


def index(request):
    def event_stream():
        for i in range(0, 100):
            yield 'data: %s\n\n' % i
            time.sleep(1)
    return StreamingHttpResponse(event_stream(), content_type='text/event-stream')

我们可以用Postman测试这个接口,效果如下图。

作者:Gacfox
版权声明:本网站为非盈利性质,文章如非特殊说明均为原创,版权遵循知识共享协议CC BY-NC-ND 4.0进行授权,转载必须署名,禁止用于商业目的或演绎修改后转载。
Copyright © 2017-2024 Gacfox All Rights Reserved.
Build with NextJS | Sitemap