Flask 2.0.2中使用request.json与Postman发送POST请求时JSON解码失败问题排查
这问题我太熟了!你遇到的核心问题是WSGI的请求体输入流只能被读取一次,你的中间件先把流读空了,导致后面Flask的Request对象再去解析JSON时根本拿不到数据。
具体原因分析
在你的GlobalAuth中间件里,你创建了一个Werkzeug的Request实例并调用了request.json:
request = Request(environ) args = request.args body_args = request.json
当调用request.json时,Werkzeug会读取environ['wsgi.input']这个输入流里的所有数据来解析JSON。而WSGI规范里,这个输入流是一次性的——读完之后指针就到了末尾,后续再读取就只能拿到空内容了。
而你的Flask接口里的request对象,是在中间件处理完成之后才创建的,这时候它再去读取wsgi.input,已经没有任何数据了,自然会抛出"Failed to decode JSON object"的错误。哪怕你加了request.on_json_loading_failed也没用,因为根本就没有可解码的内容。
解决方案
有两种常用的修复方式,你可以根据需求选择:
方案1:中间件读取后重置输入流
如果中间件必须要读取JSON数据,那在读取完之后要把输入流的指针重置到开头,这样后续Flask的Request还能读到数据:
from werkzeug.wsgi import get_input_stream class GlobalAuth: ''' Simple WSGI middleware ''' def __init__(self, app): self.app = app def __call__(self, environ, start_response): # 先读取流并保存数据 input_stream = get_input_stream(environ) data = input_stream.read() # 重置流指针到开头 environ['wsgi.input'].seek(0) # 现在可以解析JSON了 request = Request(environ) args = request.args body_args = request.json # 继续处理请求 return self.app(environ, start_response)
注意:不是所有WSGI服务器的输入流都支持seek操作,这种方式可能在某些环境下不兼容。
方案2:将解析后的JSON存入environ,后续直接复用
更稳妥的方式是,中间件解析完JSON后,把结果存入environ的自定义字段里,这样Flask接口里就不用再去读取流,直接从environ里取:
import json from werkzeug.wsgi import get_input_stream class GlobalAuth: ''' Simple WSGI middleware ''' def __init__(self, app): self.app = app def __call__(self, environ, start_response): input_stream = get_input_stream(environ) data = input_stream.read() # 重置流指针,避免影响其他可能读取流的逻辑 environ['wsgi.input'].seek(0) # 解析JSON并存入environ try: body_args = json.loads(data) if data else {} environ['middleware_parsed_json'] = body_args except json.JSONDecodeError: environ['middleware_parsed_json'] = None # 继续处理请求 return self.app(environ, start_response)
然后在你的Test接口里,直接使用中间件解析好的数据:
class Test(Resource): @staticmethod def post(): # 从environ中获取中间件解析好的JSON data = request.environ.get('middleware_parsed_json', {}) return make_response(data,200)
这种方式兼容性更好,也避免了重复解析JSON的开销。
额外提醒
另外,你代码里的request.on_json_loading_failed(e="a")用法其实不对,这个方法是用来设置自定义的加载失败回调函数,不是直接传入错误信息。正确的用法应该是:
def handle_json_error(e): return make_response({"error": "Invalid JSON"}, 400) request.on_json_loading_failed = handle_json_error
不过这个在你当前的问题里不是核心,解决了流被消耗的问题之后,这个错误自然就不会出现了。
内容的提问来源于stack exchange,提问作者Anabel

