Django StreamingHttpResponse流式失效:添加json.loads()后阻塞问题排查
Django StreamingHttpResponse流式输出阻塞问题
问题场景
服务器通过流式调用OpenAI文本补全API,接收分片数据后增量解析为完整JSON对象,再通过Django的StreamingHttpResponse流式返回给客户端。返回JSON字符串时流式正常,但将字符串解析为Python字典(json.loads())后,流式输出被阻塞,所有数据缓冲后一次性返回。
相关代码片段
views.py
class StreamViewSet(ModelViewSet): ... def stream(self, request): ... json_stream = agent.get_json_objects() return StreamingHttpResponse(json_stream, content_type="application/json")
agents.py
def stream(self): yield from self.json_agent.get_json_stream()
json_agent.py
def get_data_stream(self): # 以stream=True调用OpenAI文本补全API stream_response = self.get_text_completion(user_preference, stream=True) # 创建增量JSON解析器 json_stream_parser = JsonStreamParser() for chunk in stream_response: if chunk.choices and chunk.choices[0].delta.content is not None: json_stream_parser.feed(chunk.choices[0].delta.content) yield from json_stream_parser.parse()
json_parser.py
class JsonStreamParser: """一个可解析JSON对象流的简易JSON流式解析器。 注意:仅适用于JSON对象数组,即输入必须包含有效的JSON数组,以'['开头、']'结尾。 它会解析数组中的JSON对象,忽略数组外的任何内容。 """ def __init__(self): self.buffer = "" self.index = 0 self.open_braces = 0 self.start_index = -1 self.array_started = False def feed(self, data): self.buffer += data def parse(self): parsed = [] while self.index < len(self.buffer): if self.buffer[self.index] == '[': self.array_started = True if self.buffer[self.index] == '{' and self.array_started: # 如果start_index为-1,则找到JSON对象的起始位置 if self.start_index == -1: self.start_index = self.index self.open_braces += 1 elif self.buffer[self.index] == '}': self.open_braces -= 1 # 如果open_braces为0,则找到完整的JSON对象 if self.open_braces == 0 and self.start_index != -1: j_obj = self.buffer[self.start_index:self.index+1] parsed.append(j_obj) # 启用下面代码后流式失效 # parsed.append(json.loads(j_obj)) # 重置start_index和缓冲区 self.start_index = -1 self.buffer = self.buffer[self.index:] self.index = 0 self.index += 1 return parsed def flush(self): self.buffer = "" self.index = 0
服务器配置
INSTALLED_APPS
INSTALLED_APPS = [ 'django.contrib.admin', 'django.contrib.auth', 'django.contrib.contenttypes', 'django.contrib.sessions', 'django.contrib.messages', 'django.contrib.staticfiles', 'rest_framework.authtoken', ]
REST_FRAMEWORK
REST_FRAMEWORK = { 'DEFAULT_AUTHENTICATION_CLASSES': [ 'rest_framework.authentication.TokenAuthentication', ] }
MIDDLEWARE
MIDDLEWARE = [ 'django.middleware.security.SecurityMiddleware', 'django.contrib.sessions.middleware.SessionMiddleware', 'django.middleware.common.CommonMiddleware', 'django.middleware.csrf.CsrfViewMiddleware', 'django.contrib.auth.middleware.AuthenticationMiddleware', 'django.contrib.messages.middleware.MessageMiddleware', 'django.middleware.clickjacking.XFrameOptionsMiddleware', ]
问题原因
- StreamingHttpResponse输出限制:该响应类仅支持直接输出字节或字符串。如果生成器返回Python字典(
json.loads()的结果),Django会自动调用DRF的JSONRenderer对整个响应进行序列化,而渲染器会缓冲所有数据后一次性输出,破坏流式特性。 - DRF视图的处理逻辑:使用
ModelViewSet时,DRF会对响应进行统一处理,即使返回StreamingHttpResponse,也可能触发渲染器的缓冲机制,而非直接流式输出。
解决办法
1. 保持输出为字符串类型
将解析后的字典重新序列化为JSON字符串,确保生成器输出的是字符串而非Python对象:
# 在json_parser.py的parse方法中替换为: import json parsed.append(json.dumps(json.loads(j_obj)))
如果不需要修改字典内容,直接保留原有的JSON字符串(j_obj)即可,无需解析后再序列化。
2. 绕过DRF的渲染器处理
在视图中明确禁用DRF的渲染器处理,直接返回原始的StreamingHttpResponse:
def stream(self, request): ... json_stream = agent.get_json_objects() response = StreamingHttpResponse(json_stream, content_type="application/json") # 禁用DRF渲染器的自动处理 response.accepted_renderer = None response.accepted_media_type = "application/json" response.renderer_context = {} return response
也可以改用普通函数视图替代ModelViewSet,避免DRF的额外处理流程。
3. 禁用响应缓冲相关设置
- 关闭
CommonMiddleware的ETag功能:生成ETag需要读取完整响应内容,会触发缓冲。可在视图中添加:response['ETag'] = None - 确保没有启用
GZipMiddleware等会缓冲响应流的中间件。
4. 添加流式分隔符(可选)
为方便客户端解析多个流式返回的JSON对象,可在每个JSON字符串后添加换行符:
parsed.append(json.dumps(json.loads(j_obj)) + '\n')
客户端可按行读取并逐个解析JSON对象。
内容的提问来源于stack exchange,提问作者user2647085
相关产品推荐
相关产品推荐

