Tornado stream_request_body:如何将自定义错误消息写入文档元素?

Tornado stream_request_body: how to write custom error msg to document element?

我有一个 tornado 应用程序使用 stream_request_body 将文件上传到服务器。文件选择是一个 HTML 形式,其中 JS onsubmit 函数用于执行上传处理程序。 JS函数是asyncawait fetch。如果用户选择超过最大允许大小的文件,那么我在 def prepare(self) 中使用 self.set_status(400)。在这种情况下,我还想 send/write 一个文本字符串 (self.write('File too big')?) 应该显示在文档中的一个元素中作为给用户的信息,我该怎么做?

使用我当前的 JS 脚本,我在浏览器控制台中收到错误消息:

Promise { <state>: "pending" }
TypeError: Response.json: Body has already been consumed.

我在设置 tornado 服务器时遇到的另一个问题是,尽管我在 def prepare(self) 函数中有一个 return,但当文件大于允许的最大值时,然后 def data_receiveddef post 被执行了(文件实际上是上传到服务器),为什么?

任何 help/hints 感谢。我是 tornado 和 JS 的新手,很抱歉如果问题很基础。

使用 tornado 版本 6.1,python 3.9

application.py

from tornado import version as tornado_version
from tornado.ioloop import IOLoop
import tornado.web
import uuid
import os
import json


MB = 1024 * 1024
GB = 1024 * MB
MAX_STREAMED_SIZE = 1024 #20 * GB

@tornado.web.stream_request_body
class UploadHandler(tornado.web.RequestHandler):
    def initialize(self):
        self.bytes_read = 0
        self.loaded = 0
        self.data = b''

    def prepare(self):
        self.content_len = int(self.request.headers.get('Content-Length'))

        if self.content_len > MAX_STREAMED_SIZE:
            txt = "Too big file"
            print(txt)
            self.set_status(400)
            # how do I pass this txt to an document element?
            self.write(json.dumps({'error': txt}))

            # eventhough I have a return here execution is continued
            # in data_received() and post() functions 
            # Why is that?
            return 

    def data_received(self, chunk):
        self.bytes_read += len(chunk)
        self.data += chunk

    def post(self):
        value = self.data
        fname = str(uuid.uuid4())
        with open(fname, 'wb') as f:
            f.write(value)

        data = {'filename': fname}

        print(json.dumps(data))
        self.write(json.dumps(data))
        

class IndexHandler(tornado.web.RequestHandler):
    def get(self):
        self.render('index.html')

def main():
    handlers = [(r'/', IndexHandler), (r'/upload', UploadHandler)]

    settings = dict(debug=True, template_path=os.path.dirname(__file__))

    app = tornado.web.Application(handlers, **settings)
    print(app)
    app.listen(9999, address='localhost')

    IOLoop().current().start()


if __name__ == '__main__':
    print('Listening on localhost:9999')
    print('Tornado ver:', tornado_version)
    main()

index.html

<!DOCTYPE html>
<html>
    <head>
        <meta charset="UTF-8">
        <title>Upload something!</title>
    </head>
    <body>
    <h1>Upload</h1>
    <form id="uploadForm">
        <input type="file" name="file" id="file" />
        <br />
        <input type="submit" value="Upload">
    </form>
    <p><span id='display'></span></p>
    <script>
    uploadForm.onsubmit = async (e) => {
        e.preventDefault();
        var fileInput = document.getElementById('file');
        var fileAttr = fileInput.files[0];
        console.log(fileAttr);
        var filename = fileInput.files[0].name;
        console.log(filename);

        document.getElementById('display').innerHTML = 
            'Uploading ' + document.getElementById("file").value;

        let formData = new FormData(document.getElementById('uploadForm'));
        try {
            let response = await fetch(`${window.origin}/upload`, {
                method: "POST",
                body: formData,
            });
            if (!response.ok) {
                console.log('error')
                console.log(response.json());
                // how do I update document.getElementById('display').innerHTML
                // with tornado self.write when error response?
            }
            let result = await response.json();
            console.log(result);
            document.getElementById('display').innerHTML = 'Finished';
        } catch(exception) {
            console.log(exception);
        } 
    };
    </script>
    </body>
</html>

prepare中return是不够的,你需要抛出异常来停止处理。

所以你有两个选择:

  1. 使用提供的功能:覆盖 RequestHandler 上的 write_error 以创建自定义错误响应,然后在 prepare 中覆盖 raise tornado.web.HTTPError(400)[1] print

  2. 自己动手:用self.set_status设置一个错误状态码,self.write,当场写出你需要什么,然后raise tornado.web.Finish短路请求的处理。

使用您的代码,基本上只需将 prepare 中的 return 替换为 raise tornado.web.Finish()。显然,如果您要在多个地方执行此操作,使用 #1 是有意义的,但如果您只有现在的脚本,#2 就可以了。