You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

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',
]

问题原因

  1. StreamingHttpResponse输出限制:该响应类仅支持直接输出字节或字符串。如果生成器返回Python字典(json.loads()的结果),Django会自动调用DRF的JSONRenderer对整个响应进行序列化,而渲染器会缓冲所有数据后一次性输出,破坏流式特性。
  2. 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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.26 16:34:51