【问题标题】:Using Flask send_from_directory from a Docker container returns an empty tar.gz file使用 Docker 容器中的 Flask send_from_directory 返回一个空的 tar.gz 文件
【发布时间】:2020-03-09 05:14:18
【问题描述】:

在 Docker 上尝试使用 Flask 时,我成功地通过 HTML 表单使用用户输入,运行进程,然后创建包含进程输出的 tarball。我正在尝试在一堆临时目录中进行文件处理。

我已经通过终端询问了容器文件系统:我的进程按预期运行,并且成功创建了 tar.gz 文件。 tar -xzf myfile.tar.gz 正是我想要的。

但是,当我通过网络服务器下载 tar.gz 文件时,它是空的。

我正在按如下方式创建文件:

def create_tarball(client_directory, output_directory):
    '''Creates a tarball from generated client files

    Args:
        client_directory: location of files to add to the tarball
        target_directory: location to save the created tarball
    '''
    os.chdir(output_directory)
    t = tarfile.open('client.tar.gz', mode='w:gz')
    t.add(client_directory, arcname='cloud-run-client')
    t.close()

这是/download 路线:

@app.route('/download', methods=["GET"])
def download():
    return send_from_directory(
            directory=app.config["DOWNLOAD_DIR"],
            filename='client.tar.gz',
            as_attachment=True
    )

我的烧瓶应用配置类如下所示:

import tempfile

class Config(object):
    # ...
    UPLOAD_DIR = tempfile.mkdtemp(prefix='/tmp/', dir='/')
    DOWNLOAD_DIR = tempfile.mkdtemp(prefix='/tmp/', dir='/')
    CLIENT_DIR = tempfile.mkdtemp(prefix='/tmp/', dir='/')
    #...

我也尝试过创建标准目录(而不是 Python 临时文件),但无济于事 - 与空 tarball 的结果相同。

【问题讨论】:

    标签: python docker flask


    【解决方案1】:

    仅给出代码,我无法重现该问题。

    Dockerfile:

    FROM ubuntu:18.04
    
    # python3-pip will make pip available for python3.7
    RUN apt-get update && \
        apt-get install --yes python3.7 python3-pip
    
    RUN python3.7 -m pip install -q flask
    
    WORKDIR /app
    
    COPY example.py /app
    
    ENTRYPOINT [ "python3.7" ]
    
    CMD [ "example.py" ]
    

    构建 docker 镜像(为简洁起见缓存输出):

    $ docker build -t flask-example:latest .
    Sending build context to Docker daemon  9.624MB
    Step 1/7 : FROM ubuntu:18.04
     ---> 72300a873c2c
    Step 2/7 : RUN apt-get update --quiet --yes &&     apt-get install --quiet --yes python3.7 python3-pip
     ---> Using cache
     ---> defecdbab68a
    Step 3/7 : RUN python3.7 -m pip install -q flask
     ---> Using cache
     ---> ac5d72995a2d
    Step 4/7 : WORKDIR /app
     ---> Using cache
     ---> ad1c0e2d4290
    Step 5/7 : COPY example.py /app
     ---> 8422399a2979
    Step 6/7 : ENTRYPOINT [ "python3.7" ]
     ---> Running in 10a0f0f161a5
    Removing intermediate container 10a0f0f161a5
     ---> ede44f15a658
    Step 7/7 : CMD [ "example.py" ]
     ---> Running in 385c2fab77ff
    Removing intermediate container 385c2fab77ff
     ---> c3a4febfeee6
    Successfully built c3a4febfeee6
    Successfully tagged flask-example:latest
    

    代码,带有您的示例并为 tarball 编写一个假文件:

    #!/usr/bin/python3.7
    
    from flask import Flask, escape, request, send_from_directory
    import os
    import tempfile
    import tarfile
    import datetime
    from pathlib import Path
    
    class Config(object):
        UPLOAD_DIR = tempfile.mkdtemp(prefix='/tmp/', dir='/')
        DOWNLOAD_DIR = tempfile.mkdtemp(prefix='/tmp/', dir='/')
        CLIENT_DIR = tempfile.mkdtemp(prefix='/tmp/', dir='/')
    
    app = Flask(__name__)
    app.config.from_object('example.Config')
    
    @app.route('/')
    def hello():
        name = request.args.get("name", "World")
        return f'Hello, {escape(name)}!'
    
    def create_tarball(client_directory, output_directory):
        '''Creates a tarball from generated client files
    
        Args:
            client_directory: location of files to add to the tarball
            target_directory: location to save the created tarball
        '''
        os.chdir(output_directory)
        app.logger.debug("Contents: %s", os.listdir(client_directory))
        t = tarfile.open('client.tar.gz', mode='w:gz')
        t.add(client_directory, arcname='cloud-run-client')
        t.close()
        app.logger.debug("Results: %s", os.listdir(output_directory))
    
    @app.route('/populate', methods=["GET"])
    def populate_files():
        out = Path(app.config["CLIENT_DIR"]) / 'output.txt'
        with open(out, 'w') as fhandle:
            fhandle.write(f"{datetime.datetime.now()} Example output\n")
        app.logger.debug("Contents: %s", os.listdir(app.config["CLIENT_DIR"]))
        create_tarball(app.config["CLIENT_DIR"], app.config["DOWNLOAD_DIR"])
        return '200'
    
    @app.route('/download', methods=["GET"])
    def download():
        return send_from_directory(
                directory=app.config["DOWNLOAD_DIR"],
                filename='client.tar.gz',
                as_attachment=True
        )
    
    if __name__ == '__main__':
        app.run(debug=True, host='0.0.0.0')
    

    运行容器:

    $ docker run --rm -p 5000:5000 flask-example:latest
     * Serving Flask app "example" (lazy loading)
     * Environment: production
       WARNING: This is a development server. Do not use it in a production deployment.
       Use a production WSGI server instead.
     * Debug mode: on
     * Running on http://0.0.0.0:5000/ (Press CTRL+C to quit)
     * Restarting with stat
     * Debugger is active!
     * Debugger PIN: 205-573-930
    172.17.0.1 - - [10/Mar/2020 03:55:58] "GET /?name=me HTTP/1.1" 200 -
    [2020-03-10 03:56:01,271] DEBUG in example: Contents: ['output.txt']
    [2020-03-10 03:56:01,272] DEBUG in example: Contents: ['output.txt']
    [2020-03-10 03:56:01,276] DEBUG in example: Results: ['client.tar.gz']
    172.17.0.1 - - [10/Mar/2020 03:56:01] "GET /populate HTTP/1.1" 200 -
    172.17.0.1 - - [10/Mar/2020 03:56:03] "GET /download HTTP/1.1" 200 -
    

    客户端输出:

    $ curl http://localhost:5000/?name='me'
    Hello, me!
    
    $ curl http://localhost:5000/populate
    Tarball created
    
    $ curl --output - http://localhost:5000/download | tar tzv
      % Total    % Received % Xferd  Average Speed   Time    Time     Time  Current
                                     Dload  Upload   Total   Spent    Left  Speed
    100   197  100   197    0     0  65666      0 --:--:-- --:--:-- --:--:-- 65666
    drwx------ root/root         0 2020-03-09 20:56 cloud-run-client/
    -rw-r--r-- root/root        42 2020-03-09 20:56 cloud-run-client/output.txt
    

    【讨论】:

    • 谢谢@kenners。我能看到的唯一实质性区别是您使用sudo 运行docker。我也做了同样的事情,它奏效了。然后我杀死了容器并在没有sudo 的情况下再次运行它并且仍然有效。恐怕我不明白为什么 - 但谢谢!
    • 跟进可能与这里讨论的问题有关:medium.com/redbubble/… 结合我使用 Chrome 浏览器而不是 curl 进行测试的事实。
    • 很好:sudo 只对我来说是必需的,因为我没有将我的用户添加到我的 Linux 计算机上的 'docker' 组,并且无法访问 docker sock 文件。我最近在这个开发系统上安装了 docker,还没有通过docs.docker.com/install/linux/linux-postinstall。为了清楚起见,我编辑了我的答案以删除 sudo 。我刚刚尝试在 Windows 上使用 Chrome 80.x 再次下载,并且能够看到我的示例输出文件。
    • 将问题与 Docker 与 Flask 代码中的错误隔离开来的一种方法是在容器外运行 Flask 应用程序。根据您的描述,这可能更难做到,因为您还必须在主机上运行子进程,但这对于一次性测试来说可能是可行的。
    猜你喜欢
    • 1970-01-01
    • 2021-05-11
    • 2023-01-27
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2017-03-08
    • 2019-11-22
    • 1970-01-01
    相关资源
    最近更新 更多