requests中post参数data和json区别

HTTP协议介绍

我们知道,HTTP 协议通过 ASCII 码的形式进行传输,是建立在 TCP/IP 协议之上的应用层协议,HTTP/1.1 协议中规定的请求方式有八种:OPTIONS、GET、HEAD、POST、PUT、DELETE、TRACE、CONNECT。其中通过 POST 提交表单的方式最为常见。

HTTP/1.1 协议规定将一个完整的 HTTP 请求分为三个部分,请求行,请求头和请求主体。大概框架如下:

1
2
3
4
<method> <request-URL> <version>
<headers>

<body>
1
2
3
4
POST http://httpbin.org HTTP/1.1
Content-Type: application/x-www-form-urlencoded;charset=utf-8

a_test=112233&b_test=223344

POST请求

协议规定 POST 请求的数据必须放在请求主体中,但是并没有说明提交的数据必须使用什么编码方式,所以我们完全可以自定义编码方式,只要格式满足以上协议中规定的格式即可。

由于编码方式可以自定义,在 CS 架构中,服务器不可能提前知道客户端的编码方式,所以服务器可以成功解析数据的前提就是通过某个字段告诉服务端客户端发送数据时所使用的编码方式,这个字段就是 Content-Type

在 POST 请求中,Content-Typebody 是最重要的两个数据。

POST请求编码方式

application/x-www-form-urlencoded

浏览器原生 form 表单,如果不设置 enctype,最终提交数据时所用的就是这种编码方式。这应该是最常见的编码方式了,由于浏览器原生支持,所以用途最广泛。

Content-Type 字段被设置为 application/x-www-form-urlencodedbody 数据为 key/value 的形式,通过 URL 编码后使用 & 符号进行连接。大部分的服务端都可以支持这种编码方式,好多客户端(指编程语言,不包括浏览器)的默认编码方式也是这个。

1
2
3
4
POST http://httpbin.org HTTP/1.1
Content-Type: application/x-www-form-urlencoded;charset=utf-8

a_test=112233&b_test=223344

multipart/form-data

这个是通过浏览器上传文件时所用的编码方式,在使用浏览器上传文件时,须将 form 表单的 enctype 字段设置为 multipart/form-data

这种编码方式的请求体比较长,因为包含了文件数据,通过 boundary 来分割不同字段的数据。

关于此类表单的详细信息可以查看 rfc1867

1
2
3
4
5
6
7
8
9
10
11
12
13
POST http://httpbin.org HTTP/1.1
Content-Type:multipart/form-data; boundary=----WebKitFormBoundaryrGKCBY7qhFd3TrwA

------WebKitFormBoundaryrGKCBY7qhFd3TrwA
Content-Disposition: form-data; name="text"

text
------WebKitFormBoundaryrGKCBY7qhFd3TrwA
Content-Disposition: form-data; name="file"; filename="111.jpg"
Content-Type: image/png

PNG ... content of 111.jpg ... ... ... ... ...
------WebKitFormBoundaryrGKCBY7qhFd3TrwA--

application/json

由于 JSON 规范的流行,越来越多的人开始使用这种编码方式,这种编码方式可以提交比较复杂的结构化数据,方便各种类型的数据交互。

1
2
3
4
POST http://httpbin.org HTTP/1.1 
Content-Type: application/json;charset=utf-8

{"test":"test123","test22":[111,222,333]}

text/xml

这个编码规范一般用于使用 XML 作为编码方式的远程调用规范。由于接触的不多,就不过多介绍了。

1
2
3
4
5
6
7
8
9
10
11
12
POST http://httpbin.org HTTP/1.1 
Content-Type: text/xml

<?xml version="1.0"?>
<methodCall>
<methodName>examples.getStateName</methodName>
<params>
<param>
<value><i4>41</i4></value>
</param>
</params>
</methodCall>

requests中post请求参数区别

在解释之前先提一下 httpbin.org 这个网站,这个网站的介绍是 A simple HTTP Request & Response Service. ,简单来说就是它是一个调试网站,可以通过网站返回的数据来了解我们发送给服务器的数据是怎么样的,服务器接收到了什么类型的数据,它的功能有很多,大家可以到它的官网多多了解下。

使用 requests 这个库可以通过不同的参数发送不同编码类型的数据,先看一段代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
from pprint import pprint

import requests

url = 'http://httpbin.org/post'
data = {'a_test': 112233, 'b_test': 223344}
r = requests.post(url=url, data=data).json()
pprint(r)

url = 'http://httpbin.org/post'
data = {'a_test': 112233, 'b_test': 223344}
r = requests.post(url=url, json=data).json()
pprint(r)

我们使用同一个字典,使用 data 参数和 json 参数分别发送同样的数据,来对比数据是如何被处理的,以及被处理的结果。先看打印出来的返回值:

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
26
27
28
{'args': {},
'data': '',
'files': {},
'form': {'a_test': '112233', 'b_test': '223344'},
'headers': {'Accept': '*/*',
'Accept-Encoding': 'gzip, deflate',
'Content-Length': '27',
'Content-Type': 'application/x-www-form-urlencoded',
'Host': 'httpbin.org',
'User-Agent': 'python-requests/2.24.0',
'X-Amzn-Trace-Id': 'Root=1-5f4869c6-b3834c881b400bc9b2877715'},
'json': None,
'origin': '125.36.92.42',
'url': 'http://httpbin.org/post'}
{'args': {},
'data': '{"a_test": 112233, "b_test": 223344}',
'files': {},
'form': {},
'headers': {'Accept': '*/*',
'Accept-Encoding': 'gzip, deflate',
'Content-Length': '36',
'Content-Type': 'application/json',
'Host': 'httpbin.org',
'User-Agent': 'python-requests/2.24.0',
'X-Amzn-Trace-Id': 'Root=1-5f4869c6-a9b34c351c0adeed9669ac2a'},
'json': {'a_test': 112233, 'b_test': 223344},
'origin': '125.36.92.42',
'url': 'http://httpbin.org/post'}

通过上面数据可以看出,使用 data 参数时,发送的数据默认使用 application/x-www-form-urlencoded 编码方式进行处理,然后发送了出去,证据就是我们发送的数据出现在了 form 表单字段中,而且 Content-Type 字段的值为 application/x-www-form-urlencoded,并且 json 字段的数据为 None(因为它的服务端是用 python 写的,所以会出现 None)。

接下来看使用 json 参数发送的数据,Content-Type 字段的值为 application/json,证明是通过 application/json 编码发送的数据,并且数据出现在了 json 字段中,证明服务端正常收到了 json 类型的数据并且可以正常处理。

可能有小伙伴注意到数据还出现在了 data 字段中,由于不清楚服务端是怎么样的处理逻辑,为什么发送的 application/json 编码的数据会出现在 data 字段中,这里暂时不做深究,感兴趣的小伙伴可以尝试到它的 GitHub 上看源代码找找答案。

到这里我们通过服务端返回的数据了解到了 data/json 两个参数的区别, 接下来我们通过使用 Charles 抓包的方式来具体看看我们发出去的数据是什么样的。

  • data 参数

可以看到,Content-Type 字段的值被设置为 application/x-www-form-urlencoded,而且 body 数据的格式符合我们上边所介绍的格式。

  • json 参数

可以看到 Content-Type 字段的值被设置为 application/json,而且 body 数据的格式是一个正常 json 格式的数据,同样符合我们上边所介绍的格式,而且抓包软件下方还多出来了两个选项卡 JSON/JSON Text, 说明抓包软件可以正常处理,解析,展示我们上传的数据,进一步印证了我们发送的数据是一个正常的 json 格式的数据。

综上所述,我们可以了解到,requests 库发送 post 请求时,可以直接使用 data/json 参数来切换发送请求时的编码方式,并且输入的数据都为字典,requests 会自动处理成不同的编码方式,并且将 Content-Type 字段的值自动设置为正确的编码方式的值。由于本文主要讨论 data/json 的区别,这里就不介绍如何通过 requests 发送文件了。

跟进代码查看

接下来我们跟进到 requests 的源代码中去看看 requests 对于不同的编码方式是怎么处理的。我这里使用的是 Pycharm,方便单步调试并且实时查看变量的值,大家可以选用你们喜欢的工具进行调试学习。由于是为了查看 requests 对于不同编码方式的处理,所以单步调试的操作方法我就不做过多说明了。

  • 首先打断点,跟进代码。

  • 这里调用了 request 这个函数。其实其他的请求方式也是调用这个函数,通过第一个参数来区分是什么请求类型。继续跟进

  • 由于我们直接使用 requests.post 来请求,但是 requests 是通过 Session 这个类的对象作为最小单位来进行 Cookie 持久化,连接池等操作的,所以这里通过 with 语句为我们新建了 Session 对象。

  • 继续跟进到 session.request 中,这个方法有很多参数,平时我们所用到的参数在这里都有了。我们可以看到,data 和 json 参数被传进来以后又被传到了 Request 这个类中, 新建了一个 Request 的对象,然后调用 prepare_request 函数进行了处理,我们有理由相信,prepare_request 这个函数里边会对我们传进来的 data 和 json 参数进行处理。

  • 进入到 prepare_request 函数中,可以看到,传入进来的 request 对象的实例变量的值又被传入到了 PreparedRequest 类的 prepare 函数中进行了处理,处理完成后返回了 PreparedRequest 对象。我们跟进到 prepare 函数中

  • 这里分别对我们传入的不同数据进行了校验和处理,我们跟进到 self.prepare_body 函数中查看我们感兴趣的 data/json 数据是怎么被处理的。

  • 这个函数比较长,进入函数后,首先对 json 进行处理,使用系统的 json.dumps 将字典转为字符串,然后转为 UTF8 编码,设置好 content-type 的值为 application/json。还对于 data 为一个流的情况进行了判断和处理,不多解释。

  • 跳过处理 data 流的过程,首先进行了 files 是否为 None 的处理,如果有文件就需要读取文件并且编码。如果没有的话处理 data 参数,通过 self._encode_params 函数编码 data 参数。然后将 content-type 的值设置为 application/x-www-form-urlencoded。最后将 content-type 字段添加到请求头中。

  • 最后再看一下 self._encode_params 函数,通过各种类型判断,最后将数据转换为列表,列表中是由 key/value 组成的元组,最后调用系统的 urlencode 函数将 result 中的数据转换为最终发送出去的数据。

手动发送 json

可能有小伙伴会问,以前发送 json 数据不是这么发送的,是通过 json.dumps 函数转换之后发送出去的,那可能你见到的是这样的代码:

1
2
3
4
5
6
7
8
9
10
import json
from pprint import pprint

import requests

url = 'http://httpbin.org/post'
data = json.dumps({'a_test': 112233, 'b_test': 223344})
r = requests.post(url=url, data=data, proxies={'http': 'http://192.168.233.186:8888'}).json()
pprint(r)

我们来看下服务器返回数据:

1
2
3
4
5
6
7
8
9
10
11
12
13
{'args': {},
'data': '{"a_test": 112233, "b_test": 223344}',
'files': {},
'form': {},
'headers': {'Accept': '*/*',
'Accept-Encoding': 'gzip, deflate',
'Content-Length': '36',
'Host': 'httpbin.org',
'User-Agent': 'python-requests/2.24.0',
'X-Amzn-Trace-Id': 'Root=1-5f4897ee-054e38c02d5d5d60ece7f640'},
'json': {'a_test': 112233, 'b_test': 223344},
'origin': '125.36.92.42',
'url': 'http://httpbin.org/post'}

可以看到,数据被识别出来了, json 字段的值不是 None,先别高兴的太早,我们来看看抓包结果:

可以看到,数据是抓到了,服务器也可以正常接收到并解析,但是我们发现请求头中并没有 Content-Type 字段,但是服务器仍然可以正常解析,这只能说明服务器尝试去通过 json 解析,如果解析正常的话会当作 json 请求进行处理。

为了验证猜想,我去翻了网站的源代码,证实了我的猜想是正确的。

所以这里正常返回了数据只能说明服务器在尝试,如果服务器尝试失败了可能就返回错误了,所以说我们在自己手动发送请求时还是要记得带好 Content-Type 字段,不能让别人猜测。如果服务器不去猜测呢?或者说可能没有对应的 Content-Type 字段服务器会直接返回错误,为了保证程序的健壮性,还是要记得手动添加。

虽然手动添加是可以的,但是还是推荐使用 json 参数,可以自动添加请求头,并且格式化、验证数据,防止出错。

1
2
3
4
5
6
7
8
9
10
import json
from pprint import pprint

import requests

url = 'http://httpbin.org/post'
data = json.dumps({'a_test': 112233, 'b_test': '你好'})
headers = {'Content-Type': 'application/json'}
r = requests.post(url=url, data=data, proxies={'http': 'http://192.168.233.186:8888'}).json()
pprint(r)

python 之禅中有一句话叫:**显性胜于隐性(Explicit is better than implicit)**,就是要提醒我们一定要明确。既然有了更好的格式化数据的方式,就要利用好它。

好了,以上内容就是关于 requests 中 data 参数和 json 参数区别的全部内容了,没有看明白的小伙伴可以自己动手调试下源代码,就可以理解了。

参考链接:

https://imququ.com/post/four-ways-to-post-data-in-http.html

https://github.com/postmanlabs/httpbin/blob/master/httpbin/helpers.py

http://httpbin.org/

本文章首发于个人博客 LLLibra146’s blog
本文作者:LLLibra146
版权声明:本博客所有文章除特别声明外,均采用 © BY-NC-ND 许可协议。非商用转载请注明出处!严禁商业转载!
本文链接https://blog.d77.xyz/archives/6ac51195.html