0%

http之请求参数

测试代码

server代码(python)

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
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
#!/usr/bin/env python
# coding:utf-8

import tornado.ioloop
import tornado.web


class MainHandler(tornado.web.RequestHandler):

def get(self):
print("method: ", self.request.method)
print("Content-Type: ", self.request.headers.get("Content-Type", "No-Content-Type"))
print("uri: ", self.request.uri)
print("path: ", self.request.path)
print("argument: ", self.request.arguments)
print("body: ", self.request.body)

def post(self):
print("method: ", self.request.method)
print("Content-Type: ", self.request.headers.get("Content-Type", "No-Content-Type"))
print("uri: ", self.request.uri)
print("path: ", self.request.path)
print("argument: ", self.request.arguments)
print("body: ", self.request.body)

def head(self):
"""
主要测试file
:return:
"""
print("method: ", self.request.method)
print("Content-Type: ", self.request.headers.get("Content-Type", "No-Content-Type"))
print("uri: ", self.request.uri)
print("path: ", self.request.path)
print("argument: ", self.request.arguments)
print("body: ", self.request.body)
print("body str: ")
print(self.request.body.decode())

print('-' * 20)
print("file:")
for _, l in self.request.files.items():
print('-' * 10)
for f in l:
content = f.body
filename = str(f.filename)
print("filename: ", filename)
print("content: ", content)


application = tornado.web.Application([
(r"/main", MainHandler),
])

if __name__ == "__main__":
application.listen(5001)
tornado.ioloop.IOLoop.instance().start()

server代码(golang)

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
29
30
31
32
33
34
35
36
package main

import (
"fmt"
"github.com/gin-gonic/gin"
"io/ioutil"
"net/http"
)

func main() {
router := gin.Default()

router.GET("/main", func(c *gin.Context) {
var err error
if err = c.Request.ParseForm(); err != nil {
fmt.Printf("c.Request.ParseForm failed, %+v \n", err)
}

var body []byte
if body, err = ioutil.ReadAll(c.Request.Body); err != nil {
fmt.Printf("ioutil.ReadAll, %+v \n", err)
}

fmt.Println("method: ", c.Request.Method)
fmt.Println("Content-Type: ", c.Request.Header["Content-Type"])
fmt.Println("url:", c.Request.URL)
fmt.Println("form:", c.Request.Form)
fmt.Println("body:", body)
fmt.Println("bodyString: ", string(body))

c.JSON(http.StatusOK, gin.H{"status": "ok"})
})

router.Run("0.0.0.0:5001")
}

test代码(python3)

以下执行结果中的No-Content-Type均表示请求方没有指定Content-Type

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
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
import unittest

import requests


class TestHandler(unittest.TestCase):

def test_post(self):
# 你会发现和GET毫无区别
pass

def test_delete(self):
pass

def test_v1(self):
res = requests.get("http://127.0.0.1:5001/main?k1=v1&k2=v2&k2=v22", params={"name": "你好"})
print(res.content)

def test_v2(self):
res = requests.get("http://127.0.0.1:5001/main?k1=v1&k2=v2&k2=v22", data={"name": "你好"})
print(res.content)

def test_v2_v1(self):
data = "name=你好".encode()
res = requests.get("http://127.0.0.1:5001/main?k1=v1&k2=v2&k2=v22", data=data)
print(res.content)

def test_v3(self):
res = requests.get("http://127.0.0.1:5001/main?k1=v1&k2=v2&k2=v22", json={"name": "你好"})
print(res.content)

# 文件相关
def test_v4(self):
"""
Requests支持流式上传,这允许你发送大的数据流或文件而无需先把它们读入内存。要
使用流式上传,仅需为你的请求体提供一个类文件对象即可。
:return:
"""
with open("a.txt", "rb") as fp:
res = requests.head("http://127.0.0.1:5001/main?k1=v1&k2=v2&k2=v22", data=fp)
print(res.content)

def test_v5(self):
with open("a.txt", "rb") as fp:
files = {"file1": (fp.name, fp, "multipart/form-data")}

res = requests.head("http://127.0.0.1:5001/main?k1=v1&k2=v2&k2=v22", files=files)
print(res.content)

请求参数

按照python requests包来使用,各个test的执行结果如下

test_get_v1

1
2
3
4
5
6
method:  GET
Content-Type: No-Content-Type
uri: /main?k1=v1&k2=v2&k2=v22&name=%E4%BD%A0%E5%A5%BD
path: /main
argument: {'k2': [b'v2', b'v22'], 'name': [b'\xe4\xbd\xa0\xe5\xa5\xbd'], 'k1': [b'v1']}
body: b''
1
2
3
4
5
method:  GET
Content-Type: []
url: /main?k1=v1&k2=v2&k2=v22&name=%E4%BD%A0%E5%A5%BD
form: map[k1:[v1] k2:[v2 v22] name:[你好]]
body: []

test_get_v2

1
2
3
4
5
6
method:  GET
Content-Type: application/x-www-form-urlencoded
uri: /main?k1=v1&k2=v2&k2=v22
path: /main
argument: {'name': [b'\xe4\xbd\xa0\xe5\xa5\xbd'], 'k2': [b'v2', b'v22'], 'k1': [b'v1']}
body: b'name=%E4%BD%A0%E5%A5%BD'
1
2
3
4
5
6
method:  GET
Content-Type: [application/x-www-form-urlencoded]
url: /main?k1=v1&k2=v2&k2=v22
form: map[k1:[v1] k2:[v2 v22]]
body: [110 97 109 101 61 37 69 52 37 66 68 37 65 48 37 69 53 37 65 53 37 66 68]
bodyString: name=%E4%BD%A0%E5%A5%BD

test_get_v2_v1

1
2
3
4
5
6
method:  GET
Content-Type: No-Content-Type
uri: /main?k1=v1&k2=v2&k2=v22
path: /main
argument: {'k2': [b'v2', b'v22'], 'k1': [b'v1']}
body: b'name=\xe4\xbd\xa0\xe5\xa5\xbd'
1
2
3
4
5
6
method:  GET
Content-Type: []
url: /main?k1=v1&k2=v2&k2=v22
form: map[k1:[v1] k2:[v2 v22]]
body: [110 97 109 101 61 228 189 160 229 165 189]
bodyString: name=你好

test_get_v3

1
2
3
4
5
6
method:  GET
Content-Type: application/json
uri: /main?k1=v1&k2=v2&k2=v22
path: /main
argument: {'k2': [b'v2', b'v22'], 'k1': [b'v1']}
body: b'{"name": "\\u4f60\\u597d"}'
1
2
3
4
5
6
method:  GET
Content-Type: [application/json]
url: /main?k1=v1&k2=v2&k2=v22
form: map[k1:[v1] k2:[v2 v22]]
body: [123 34 110 97 109 101 34 58 32 34 92 117 52 102 54 48 92 117 53 57 55 100 34 125]
bodyString: {"name": "\u4f60\u597d"}

test_get_v4

1
2
3
4
5
6
method:  GET
Content-Type: multipart/form-data
uri: /main?k1=v1&k2=v2&k2=v22
path: /main
argument: {'k1': [b'v1'], 'k2': [b'v2', b'v22']}
body: b'{"name": "\\u4f60\\u597d"}'
1
2
3
4
5
6
method:  GET
Content-Type: [multipart/form-data]
url: /main?k1=v1&k2=v2&k2=v22
form: map[k1:[v1] k2:[v2 v22]]
body: [123 34 110 97 109 101 34 58 32 34 92 117 52 102 54 48 92 117 53 57 55 100 34 125]
bodyString: {"name": "\u4f60\u597d"}

结论

  • 在http协议中,并没有规定GET不能再使用Body传递参数!!!
  • 在http协议中,也没有规定GET请求的Content-Type,所以,Content-Type也可以是任意类型。
  • GET请求和POST请求在处理请求参数时,实际上没有任何差异。

揭秘

那么为什么很多人有GET请求只能在请求行参数中传递参数,而POST就可以在BODY中传递参数呢?这是因为大多数人都遵循网页中的form表单提交get,和post请求来了。在网页form表单中,把 form 的 method 设置为 post, 表单数据会放在 body 中,而 method 为 get(默认值) 时, 提交时浏览器会把表单中的字符拼接到 action 的 URL 后作为 query parameter 传送。于是乎就有了这么一种假像:HTTP GET 必须通过 URL 的查询参数来发送数据。

一个但是

但是,即使GET请求在http协议中并没有规定不能在body中携带参数,我们也应该这么做!!!这是因为get请求从http协议的设计初衷,就是为了获取网络资源,它的请求行uri和host就应该是它全部携带的payload,这在很多缓存系统,很多后端服务框架中都是默认的。一个GET请求,最好就不要携带一堆payload了。

rfc7231有如下的描述(链接):

A payload within a GET request message has no defined semantics;
sending a payload body on a GET request might cause some existing implementations to reject the request.

参考

rfc7231:https://tools.ietf.org/html/rfc7231#section-4.3.1