使用Python搭建简单服务器的时候,只能收到headers,收不到POST内容的情况
最近做了一个简单的http服务器,用于接收IoT端上传的json格式的日志。
服务端使用socket中的方法接收原始数据,再对原始数据单独进行处理,根据报文头部的GET或者POST等方法进行具体的操作。
sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
sock.bind((self.host, self.port))
sock.listen(128)
(client, address) = self.sock.accept()
th = Thread(target=self.accept_request, args=(client, address))
def accept_request(client, address):
data = client_sock.recv(8192)
req = data.decode("utf-8")
# 处理数据的具体方法
response = process_response(req, client_addr)
client_sock.send(response)
# clean up
client_sock.shutdown(1)
client_sock.close()
上面的代码是大概的处理流程。在这个流程中,我发现处理POST的数据的时候,经常只能收到headers部分,payload部分的数据不管发几次都是空的。而且接收缓冲区已经扩大到了8192字节,headers部分的长度只有200多字节,IoT端上传的payload也不过上百字节,不应该是超出缓冲区的问题。
=====2024.01.04更新=======
下面的方法突然某一天就失效了,我不清楚是不是python的环境变化导致的。
其实用flask接收和处理数据会更加方便快捷:
from flask import Flask, request
# 实例化flask
app = Flask(__name__)
# 接收和处理post的方法
@app.route('/test', methods=['POST'])
def recv_file_www():
# post的传入参数
print(request.args.to_dict())
# post所承载的数据
print(request.data)
return 'OK'
if __name__ == '__main__':
app.run(port=31408, host="0.0.0.0",debug=True)
上面就是一个完整的post接收和处理程序了,不需要调试socket,也不需要自己处理ResponseBuilder,最重要的是能保证获取到post数据。
======以下是原来的内容==========
经过网上搜索与自己尝试,发现socket.recv返回的时候是分段返回的,貌似会按照实际内容切割返回,必须不断的recv才能完整接收数据。有一种解决方案是POST传送之前先确定报文的完整擦换个年度,然后利用while循环,接收到对应长度才结束。
但是POST发送的一个不定长的数据,如果每次都通过http报文先传送post完整长度的话,有些浪费资源。于是我想到了这么一个方案:
POST的报文头会包含一个Content-length
的数据,记录的是payload的长度。那我在接收阶段就对headers部分进行解码和处理,获取到content-length后,在加上本次获取headers的长度就是POST的完整长度了。
那么将上面代码的client_sock.recv()
部分改写成下面的方法:
def recv(self, client_sock):
lrecved = 8192 # 记录每次接收的长度
lrecv_all=0 # 记录数据总长度
data_body = bytes() # 接收的所有数据存入这里
payload_len = 0 # 解析的payload长度
payload_flag = 0 # 用于记录是否接收到了payload,以及实时记录接收到payload的长度
while True :
part_body= client_sock.recv(8192) # 循环接收数据
data_body += part_body # 将接收的数据写入data_body
lrecved = len(part_body) # 获取本次接收长度
lrecv_all += lrecved # 计算总长度
if payload_flag == 0: # 没有接收到payload部分时,才进入下面的判断流程
if data_body[0:3].decode() == 'GET': # 由于我get方法没出现过断开的问题,因此直接返回即可。
break
elif data_body[0:4].decode() == 'POST':
payload_len = count_payload_len(data_body) # 解析头部,计算payload部分的长度
if lrecved < payload_len:
# 偶尔会出现能接收到payload的情况,所以多加了一个判断。
# 能接收payload肯定总长度大于payload_len的
print("no payloads, keep recving...")
# 计算post报文的总长度,借用payload_flag变量
payload_flag = lrecved + payload_len
# 当接收到的总长度 >= POST的计算长度,表示所有数据接收完成,退出
if payload_flag <= lrecv_all:
break
return data_body
里面解析headers的代码如下:
def count_payload_len(data):
for d in data.split(b'\r\n'): # 通过\r\n来分隔报文头
if "LENGTH" in d.upper().decode(): # 如果报文头部有length关键词,当然可以写完整,我的heasers没有其他的length就简化了
return int(d.decode().split(":")[1]) # 分隔出payload长度并返回
return 0
上面的代码其实写的很复杂,因为我解析POST整体报文的功能已经写完并且通过第三方http报文调试工具验证过了,上IoT设备实验的时候才发现了问题,本着少改一点是一点的原则,就把这一部分复杂化了,但确实解决了我的问题,就OK了。
对了,这篇博客就是利用这个这个方案改造的新服务端进行上传同步的,之前用GitHub Actions进行同步博客,每次运营商都会发一条高危访问的短信和邮件通知我,太烦了。