Pulpcode

捕获,搅碎,拼接,吞咽

0%

python requests post发送unicode丢失字符串

之前同事遇到一个问题,最后被我解决了,现在在这里分析一下。

错误描述

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
d = {
"name": "爱德华蒂奇",
"age": 12 }

headers = {
'Content-Type':'application/json',
'content-encoding' :"utf-8"
}

body = json.dumps(d, sort_keys=True, separators=(',',':'), ensure_ascii=False)

data = body.decode('utf-8')

print body
print body[: len(data)]

resp = requests.post("http://localhost:8888/json", data=data, headers=headers)

我将一个unicode字符串通过requests的post方法发送数据,结果在服务器端打印数据时,数据部分丢失。

原串儿:{"age":12,"name":"爱德华蒂奇"}

服务器收到结果:{"age":12,"name":"爱德

先说原因:

是因为requests.post("http://localhost:8888/json", data=data, headers=headers)时,data是一个unicode,而requests库以unicode的方式计算了data长度,并设置的了headers的Content-Length,而tornado服务器在读取请求的时候使用了Content-Length,从而使部分数据丢失(被截取)

简单校验

计算str s = '{"age":12,"name":"爱德华蒂奇"}' ; len(s)的长度为len1
计算unicode u = u'{"age":12,"name":"爱德华蒂奇"}' ; len(u)的长度为len2

而服务器收到的截获数据刚好缺了len1-len2。

严谨验证:

阅读requests源码,断点深入post方法:在init.py—> api.py—>models.py
在代码477行,使用了自己的utils包,计算了长度,并设置了Content-Length:

1
self.headers['Content-Length'] = builtin_str(l)

如果修改为str的长度,一切正常,不会有截获。

相关知识:

  1. unicode与str
1
2
3
4
5
s = "你好"
len(s) = 6 # utf-8环境,其它会有不同

s = u"你好"
len(s) = 2
  1. HTTP Content-Length

Content-Length首部告诉浏览器报文中实体主体的大小。这个大小是包含了内容编码的,比如对文件进行了gzip压缩,Content-Length就是压缩后的大小(这点对我们编写服务器非常重要)。除非使用了分块编码,否则Content-Length首部就是带有实体主体的报文必须使用的。使用Content-Length首部是为了能够检测出服务器崩溃而导致的报文截尾,并对共享持久连接的多个报文进行正确分段。

  1. 检测截尾

HTTP的早期版本采用关闭连接的办法来划定报文的结束。但是,没有Content-Length的话,客户端无法区分到底是报文结束时正常的关闭连接还是报文传输中由于服务器崩溃而导致的连接关闭。客户端需要通过Content-Length来检测报文截尾。

报文截尾的问题对缓存代理服务器来说尤为重要。如果缓存服务器收到被截尾的报文却没有识别出截尾的话,它可能会存储不完整的内容并多次使用他来提供服务。缓存代理服务器通常不会为没有显式Content-Length首部的HTTP主体做缓存,以此来减小缓存已截尾报文的风险。

  1. Content-Length与持久连接

Content-Length首部对于持久链接是必不可少的。如果响应通过持久连接传送,就可能有另一条HTTP响应紧随其后。客户端通过Content-Length首部就可以知道报文在何处结束,下一条报文从何处开始。因为连接是持久的,客户端无法依赖连接关闭来判断报文的结束。

有一种情况,使用持久连接可以没有Content-Length首部,即采用分块编码 (chunked encoding)时。在分块编码的情况下,数据是分为一系列的块来发送的,没块都有大小说明。哪怕服务器在生成首部的时候不知道整个实体的大小(通常是 因为实体是动态生成的),仍然可以使用分块编码传输若干已知大小的块。