Flask send_file 返回 200 OK 但不发送文件

Posted

技术标签:

【中文标题】Flask send_file 返回 200 OK 但不发送文件【英文标题】:Flask send_file returns 200 OK but does not send file 【发布时间】:2022-01-21 05:26:46 【问题描述】:

我有一个相当简单的应用程序,可以使用 Flask send_file 函数下载单个静态文件。无论我是在 Flask run 还是 gunicorn 下运行它,行为都是一样的。路由执行代码,返回 200 OK,但正文中没有数据。下面是代码。我真的很难过。我已阅读有关此主题的所有其他问题,但没有任何效果。 首先是路线:有两条,因为我尝试了两种不同的方法

def download():
    print ("download route, basedir and request method",  basedir, request.method)
    form = DownLoadForm()
    id = 1
    if form.validate_on_submit():   
        mac = request.form['macaddr']
        mac = mac.lower()
        downloadFile(mac)
    whitelist = Whitelist.query.all()
#    print(whitelist)
    return render_template('dnload.html', whitelist=whitelist, form=form)

@app.route('/downloadLog<int:id>', methods=['GET', 'POST'])
def downloadLog(id):
    print ("downloadLog request method", request.method)
    download = Whitelist.query.get_or_404(id)
    mac = download.mac
    print("DownloadLog mac: ", mac)
    downloadFile(mac)
    whitelist = Whitelist.query.all()
    return render_template('dnload.html', whitelist = whitelist, form = DownLoadForm())

def downloadFile(mac):
    logFileName = "log"+ mac+".csv"
    path = os.path.join(app.config['LOG_FOLDER'], mac)
    path = os.path.join(basedir, path)
#    print ("path in downloadFile: ", path)
    if os.path.isfile(path + '/' + logFileName):
        filepath = os.path.join(path, logFileName)
        print("Downloading Log File: ", filepath)
        #this is where the file download occurs
#        return send_file(filepath,
 #                       mimetype ='text/csv',
  #                      attachment_filename = logFileName,
   #                     as_attachment=True)
        # f = open(filepath, "r")
        # for x in range(5):
            # a = f.readline()
            # print(a)
        # f.close()
        return send_from_directory(path, logFileName, as_attachment=True, mimetype = 'text/plain')
    else:
        flash('Error, no mac logs available for download')

其次,downloadLog路由的HTML是

<td>
<a href="url_for('.downloadLog', id=mac.id) ">Download mac.id</a>
</td>

下载路径的 HTML 是 wtf.quick_form。 forms.py 代码是

    macaddr = StringField('MAC Address', validators=[DataRequired()])
    submit = SubmitField("Download MAC Log File")
这是不包含文件的 OK 内容的wireshark 显示。
0010   04 23 99 bc 40 00 40 06 1b 7c c0 a8 00 23 c0 a8   .#..@.@..|...#..
0020   00 29 13 88 f0 ea 51 28 d6 5b 94 37 8f 3a 50 19   .)....Q(.[.7.:P.
0030   01 f5 37 f8 00 00 74 2e 74 69 6d 65 73 74 61 6d   ..7...t.timestam
0040   70 32 3b 0a 20 20 20 20 63 6f 6e 73 74 20 6e 6f   p2;.    const no
0050   5f 73 75 66 66 69 78 20 3d 20 65 6c 65 6d 2e 64   _suffix = elem.d
0060   61 74 61 73 65 74 2e 6e 6f 73 75 66 66 69 78 3b   ataset.nosuffix;
0070   0a 20 20 20 20 63 6f 6e 73 74 20 75 6e 69 74 73   .    const units
0080   20 3d 20 65 6c 65 6d 2e 64 61 74 61 73 65 74 2e    = elem.dataset.
0090   75 6e 69 74 73 3b 0a 20 20 20 20 6c 65 74 20 61   units;.    let a
00a0   72 67 73 20 3d 20 5b 5d 3b 0a 20 20 20 20 69 66   rgs = [];.    if
00b0   20 28 66 6f 72 6d 61 74 29 0a 20 20 20 20 20 20    (format).      
00c0   20 20 61 72 67 73 2e 70 75 73 68 28 66 6f 72 6d     args.push(form
00d0   61 74 29 3b 0a 20 20 20 20 69 66 20 28 74 69 6d   at);.    if (tim
00e0   65 73 74 61 6d 70 32 29 0a 20 20 20 20 20 20 20   estamp2).       
00f0   20 61 72 67 73 2e 70 75 73 68 28 6d 6f 6d 65 6e    args.push(momen
0100   74 28 74 69 6d 65 73 74 61 6d 70 32 29 29 3b 0a   t(timestamp2));.
0110   20 20 20 20 69 66 20 28 6e 6f 5f 73 75 66 66 69       if (no_suffi
0120   78 29 0a 20 20 20 20 20 20 20 20 61 72 67 73 2e   x).        args.
0130   70 75 73 68 28 6e 6f 5f 73 75 66 66 69 78 29 3b   push(no_suffix);
0140   0a 20 20 20 20 69 66 20 28 75 6e 69 74 73 29 0a   .    if (units).
0150   20 20 20 20 20 20 20 20 61 72 67 73 2e 70 75 73           args.pus
0160   68 28 75 6e 69 74 73 29 3b 0a 20 20 20 20 65 6c   h(units);.    el
0170   65 6d 2e 74 65 78 74 43 6f 6e 74 65 6e 74 20 3d   em.textContent =
0180   20 74 69 6d 65 73 74 61 6d 70 5b 66 75 6e 63 5d    timestamp[func]
0190   2e 61 70 70 6c 79 28 74 69 6d 65 73 74 61 6d 70   .apply(timestamp
01a0   2c 20 61 72 67 73 29 3b 0a 20 20 20 20 65 6c 65   , args);.    ele
01b0   6d 2e 63 6c 61 73 73 4c 69 73 74 2e 72 65 6d 6f   m.classList.remo
01c0   76 65 28 27 66 6c 61 73 6b 2d 6d 6f 6d 65 6e 74   ve('flask-moment
01d0   27 29 3b 0a 20 20 20 20 65 6c 65 6d 2e 73 74 79   ');.    elem.sty
01e0   6c 65 2e 64 69 73 70 6c 61 79 20 3d 20 22 22 3b   le.display = "";
01f0   0a 7d 0a 66 75 6e 63 74 69 6f 6e 20 66 6c 61 73   ..function flas
0200   6b 5f 6d 6f 6d 65 6e 74 5f 72 65 6e 64 65 72 5f   k_moment_render_
0210   61 6c 6c 28 29 20 7b 0a 20 20 20 20 63 6f 6e 73   all() .    cons
0220   74 20 6d 6f 6d 65 6e 74 73 20 3d 20 64 6f 63 75   t moments = docu
0230   6d 65 6e 74 2e 71 75 65 72 79 53 65 6c 65 63 74   ment.querySelect
0240   6f 72 41 6c 6c 28 27 2e 66 6c 61 73 6b 2d 6d 6f   orAll('.flask-mo
0250   6d 65 6e 74 27 29 3b 0a 20 20 20 20 6d 6f 6d 65   ment');.    mome
0260   6e 74 73 2e 66 6f 72 45 61 63 68 28 66 75 6e 63   nts.forEach(func
0270   74 69 6f 6e 28 6d 6f 6d 65 6e 74 29 20 7b 0a 20   tion(moment) . 
0280   20 20 20 20 20 20 20 66 6c 61 73 6b 5f 6d 6f 6d          flask_mom
0290   65 6e 74 5f 72 65 6e 64 65 72 28 6d 6f 6d 65 6e   ent_render(momen
02a0   74 29 3b 0a 20 20 20 20 20 20 20 20 63 6f 6e 73   t);.        cons
02b0   74 20 72 65 66 72 65 73 68 20 3d 20 6d 6f 6d 65   t refresh = mome
02c0   6e 74 2e 64 61 74 61 73 65 74 2e 72 65 66 72 65   nt.dataset.refre
02d0   73 68 3b 0a 20 20 20 20 20 20 20 20 69 66 20 28   sh;.        if (
02e0   72 65 66 72 65 73 68 20 26 26 20 72 65 66 72 65   refresh && refre
02f0   73 68 20 3e 20 30 29 20 7b 0a 20 20 20 20 20 20   sh > 0) .      
0300   20 20 20 20 20 20 28 66 75 6e 63 74 69 6f 6e 28         (function(
0310   65 6c 65 6d 2c 20 69 6e 74 65 72 76 61 6c 29 20   elem, interval) 
0320   7b 0a 20 20 20 20 20 20 20 20 20 20 20 20 20 20   .              
0330   20 20 73 65 74 49 6e 74 65 72 76 61 6c 28 66 75     setInterval(fu
0340   6e 63 74 69 6f 6e 28 29 20 7b 0a 20 20 20 20 20   nction() .     
0350   20 20 20 20 20 20 20 20 20 20 20 20 20 20 20 66                  f
0360   6c 61 73 6b 5f 6d 6f 6d 65 6e 74 5f 72 65 6e 64   lask_moment_rend
0370   65 72 28 65 6c 65 6d 29 3b 0a 20 20 20 20 20 20   er(elem);.      
0380   20 20 20 20 20 20 20 20 20 20 7d 2c 20 69 6e 74             , int
0390   65 72 76 61 6c 29 3b 0a 20 20 20 20 20 20 20 20   erval);.        
03a0   20 20 20 20 7d 29 28 6d 6f 6d 65 6e 74 2c 20 72       )(moment, r
03b0   65 66 72 65 73 68 29 3b 0a 20 20 20 20 20 20 20   efresh);.       
03c0   20 7d 0a 20 20 20 20 7d 29 0a 7d 0a 64 6f 63 75    .    )..docu
03d0   6d 65 6e 74 2e 61 64 64 45 76 65 6e 74 4c 69 73   ment.addEventLis
03e0   74 65 6e 65 72 28 22 44 4f 4d 43 6f 6e 74 65 6e   tener("DOMConten
03f0   74 4c 6f 61 64 65 64 22 2c 20 66 6c 61 73 6b 5f   tLoaded", flask_
0400   6d 6f 6d 65 6e 74 5f 72 65 6e 64 65 72 5f 61 6c   moment_render_al
0410   6c 29 3b 0a 3c 2f 73 63 72 69 70 74 3e 0a 0a 20   l);.</script>.. 
0420   20 3c 2f 62 6f 64 79 3e 0a 3c 2f 68 74 6d 6c 3e    </body>.</html>
0430   0a                                                .

这是控制台输出:

    * Detected change in '/home/mycelium/serveFW/app/routes.py', reloading
    * Restarting with stat
    * Debugger is active!
    * Debugger PIN: 125-099-663
    download route, basedir and request method /home/mycelium/serveFW POST
    Downloading Log File:  /home/mycelium/serveFW/log/fcf5c4abade5/logfcf5c4abade5.csv
    192.168.0.41 - - [18/Dec/2021 13:14:43] "POST /download HTTP/1.1" 200 -
    download route, basedir and request method /home/mycelium/serveFW POST
    Downloading Log File:  /home/mycelium/serveFW/log/fcf5c4abade5/logfcf5c4abade5.csv
    192.168.0.41 - - [18/Dec/2021 13:14:52] "POST /download HTTP/1.1" 200 -
    downloadLog request method GET
    DownloadLog mac:  fcf5c4abade5
    Downloading Log File:  /home/mycelium/serveFW/log/fcf5c4abade5/logfcf5c4abade5.csv
    192.168.0.41 - - [18/Dec/2021 13:38:05] "GET /downloadLog1 HTTP/1.1" 200 -

【问题讨论】:

您没有从send_from_directory 返回返回值。也就是封装文件下载的响应对象。 我不明白你的评论。我如何不使用 ```return send_from_directory(path, l....) 返回值 仔细检查您的代码。 downloadFile 返回响应,但您将其丢弃在 download()downloadLog() 中。 【参考方案1】:

如果我也使用 make_response,它的行为方式相同。某处基本断开连接,配置等。我修改了响应,如下所示:

def downloadFile(mac):
    logFileName = "log"+ mac+".csv"
    path = os.path.join(app.config['LOG_FOLDER'], mac)
    path = os.path.join(basedir, path)
#    print ("path in downloadFile: ", path)
    if os.path.isfile(path + '/' + logFileName):
        filepath = os.path.join(path, logFileName)
        print("Downloading Log File: ", filepath)
        #this is where the file download occurs
#        return send_file(filepath,
 #                       mimetype ='text/csv',
  #                      attachment_filename = logFileName,
   #                     as_attachment=True)

        len = os.path.getsize(filepath)
        response = make_response(send_from_directory(path, logFileName, as_attachment=True), 201)
        response.headers['Content-Type'] = 'text/html'   #this is a test based on what browser says it's accepting.
        response.headers['Content-Disposition: attachment; filename'] = logFileName
        response.headers['Content-Length'] = len
        print("Just before the return,  And length", len)
        return response
    else:
        flash('Error, no mac logs available for download')

控制台和wireshark的响应基本上和上面一样。我就是不明白,不管我在别处读过的所有帖子和承诺如何,我尝试的一切似乎都不重要。这是控制台输出:

    * Detected change in '/home/mycelium/serveFW/app/routes.py', reloading
    * Restarting with stat
    * Debugger is active!
    * Debugger PIN: 125-099-663
    downloadLog request method POST
    DownloadLog mac:  fcf5c4abade5
    Downloading Log File:  
    /home/mycelium/serveFW/log/fcf5c4abade5/logfcf5c4abade5.csv
    Just before the return,  And length 183305
    192.168.0.41 - - [18/Dec/2021 21:49:00] "POST /downloadLog1 HTTP/1.1" 200 -

【讨论】:

以上是关于Flask send_file 返回 200 OK 但不发送文件的主要内容,如果未能解决你的问题,请参考以下文章

来自 Flask send_file 的 Zip 文件无效

Flask:send_file() 完成后从服务器删除文件

Flask send_file request

flask send_file使用

Python Flask send_file StringIO 空白文件

flask返回数据的五种方式