当我在 Django 中使用 StreamingHttpResponse 时下载无法完成

Download can't finish when I use StreamingHttpResponse in Django

我在 Google 云 运行 上使用 Django。自从我更新 docker 图片后,下载文件页面就无法正常工作。下载文件页面程序如下

def file_iterator(file, chunk_size=512):
    with open(file, 'rb') as f:
        while True:
            c = f.read(chunk_size)
            if c:
                yield c
            else:
                break

def download_view(request, format, pk):
    user = get_user(request)
    sentence = Sentence.objects.get(pk=pk)
    if not default_storage.exists(sentence.file.name):
        logger.warning(f'This file has been deleted pk: {pk} user: {user} file_name:{sentence.file.name}')
        raise Http404()
    
    file_name = f'{user.pk}{sentence.pk}.{format}'
    file_path = sentence.file.name
    if format == 'mp3':
        with open(file_path,'wb') as f:
            f.write(sentence.file.read())
    elif format == 'wav':
        file_path = AudioSplitter().mp3_to_wav(sentence)
    else:
        return HttpResponse('format error')
    
    response = StreamingHttpResponse(file_iterator(file_path))
    response['Content-Type'] = 'audio/mpeg' if format == 'mp3' else 'audio/wav'
    response['Content-Disposition'] = f'attachment;filename="{file_name}"'
    return response

Google云运行有下载文件限制。所以这个程序使用了 StreamingHttpResponse。它是 return 分块文件数据。当我从这个视图下载文件时,下载文件堆积在 200 ~ 224KB 左右。

这个问题发生在2021年8月17日,我当天构建了3个镜像。

  1. 2021/08/17 10:24
  2. 2021/08/17 15:54
  3. 2021/08/17 20:58

第一个容器可以下载文件。但是第二个和第三个容器无法从视图中下载文件。而且所有构建的容器都无法在第一个容器之后下载文件。

第一个容器和第二个容器有什么区别?

我在 admin.py 中更改了一行它只是添加了 list_display_links = None

我检查了 python 模块版本和 python 版本。然而,它是一样的。

当我运行docker images的时候,发现图片大小不一样

REPOSITOR              TAG       IMAGE ID       CREATED          SIZE
gcr.io/***/***       8f6ae66   62d086d8f30c   7 weeks ago      2.07GB <-- 1st
gcr.io/***/***       bdf4a43   f67948780215   7 weeks ago      2.39GB <-- 2nd

第一个容器是 2.07GB,第二个容器是 2.39GB。我的 docker 文件正在使用 FROM python:3.9。 8 月 17 日 python3.9 图片有更新吗??

我用第一张图片构建了相同的代码。当然,这个图像有下载页面问题,容器大小为 2.28GB。它比第一张图片大。 Python 模块有一些差异,python 版本已更新至 python 3.9.7.

我认为这个问题来自 python 的 docker 图片。我该如何解决这个问题?

更新

  1. 我把图片改成了python: 3.8。问题已复现
  2. 下载堆叠时发现这个错误。 uwsgi_response_write_body_do() TIMEOUT !!!

环境

第一张和第二张图片环境。


# nginx-app.conf

# the upstream component nginx needs to connect to
upstream django {
    server unix:/code/app.sock; # for a file socket
    # server 127.0.0.1:8001; # for a web port socket (we'll use this first)
}

# configuration of the server
server {
    # the port your site will be served on, default_server indicates that this server block
    # is the block to use if no blocks match the server_name
    listen      8080;

    # the domain name it will serve for
    server_name xxx.com; # substitute your machine's IP address or FQDN
    charset     utf-8;

    # max upload size
    client_max_body_size 10M;   # adjust to taste
    # set timeout
    uwsgi_read_timeout 900;
    proxy_read_timeout 900;
    # Django media
    location /media  {
        alias /code/app/media;  # your Django project's media files - amend as required
    }

    location /static {
        alias /code/app/static; # your Django project's static files - amend as required
    }

    # Finally, send all non-media requests to the Django server.
    location / {
        uwsgi_pass  django;
        include     /code/uwsgi_params; # the uwsgi_params file you installed
    }
}



[uwsgi]
# this config will be loaded if nothing specific is specified
# load base config from below
ini = :base

# %d is the dir this configuration file is in
socket = %dapp.sock
master = true
processes = 4
max-requests = 1000                  ; Restart workers after this many requests
max-worker-lifetime = 3600           ; Restart workers after this many seconds
reload-on-rss = 512                  ; Restart workers after this much resident memory
worker-reload-mercy = 60             ; How long to wait before forcefully killing workers


我解决了这个问题。我认为它是由这个错误引起的。

uwsgi_response_write_body_do() TIMEOUT !!!

我添加睡眠并更改块大小。

# change chunk size from 512 byte to 1 MB, because it sleep 0.1 sec each loop.

def file_iterator(file, chunk_size=1024 * 1024):
    with open(file, 'rb') as f:
        while True:
            time.sleep(0.1) # <-- add sleep
            c = f.read(chunk_size)
            if c:
                yield c
            else:
                break

P.S。我试过了this solution。但是对我没有影响。

更新

当我检查自己下载的时候。它工作正常。但是请求延迟非常慢。大约15~18分钟。当我 运行 第一张图片时,大约是 0.2 ~ 0.3 秒。并且 Charged 容器实例时间增加了 5 倍。

1 小时

1 天

我觉得这个解决方案是急救。我钻了一些答案真正的解决方案。

更新 2021/11/02

我找到了真正的解决方案! uWSGI 套接字连接有一些问题。我改变了端口连接。现在我的申请 return 结果非常正常。

注意:这是正常的uWSGI PORT连接设置。