Need help merging Tornado stream_request_body into Bokeh Div widget

I have a Tornado upload handler for streaming a file to a server and my objective is to have this upload handler work through a Bokeh Div widget.

The upload handler uses tornado.web.stream_request_body decorator and through a HTML form (input file selector and input submit button) I activate the POST request with javascript using fetch. By itself, without Bokeh, the Tornado upload handler works as expected. However, when I try to add the form through Bokeh Div widget it does not work. In Div I set text equal to

UPLOAD_DIV = '''
    <form id="uploadForm">
        <input type="file" name="file" id="file" />
        <br/>
        <input type="submit" value="Upload" onclick="uploadFn">
    </form>
    <p><span id='display'></span></p>
'''

Warning in browser console

Form contains a file input, but is missing method=POST and enctype=multipart/form-data on the form.  The file will not be sent.

The warning says that POST and enctype is missing, but I have defined that in the JS fetch function

<script>
    async function uploadFn(e) {
    //uploadForm.onsubmit = async (e) => {
        e.preventDefault();
        var fileInput = document.getElementById('file');
        var filename = fileInput.files[0].name;
        console.log(filename);
        
        document.getElementById('display').innerHTML = 
            'Uploading ' + document.getElementById("file").value;

        let response = await fetch(`${window.origin}/upload`, {
            method: "POST",
            body: new FormData(uploadForm)
        });
        let result = await response.json();
        console.log(result);
        document.getElementById('display').innerHTML = 'Finished'
    };
    </script>

In order to have the upload endpoint I start Tornado and Bokeh programmatically.

I am wondering if anyone knows what I am doing wrong or got any hints to how I can achieve my objective? Thanks.

Using Bokeh 2.3.0, FireFox, Redhat 7.0

application.py

from bokeh.application import Application
from bokeh.application.handlers import DirectoryHandler
from bokeh.server.server import Server
from bokeh.util.browser import view
from bokeh import __version__ as bk_ver
from tornado.web import RequestHandler
from upload_handler import UploadHandler
import os


PORT = 5006

def create_bokeh_server(file, port):
    """Start bokeh server with applications paths"""

    bokeh_app = Application(DirectoryHandler(filename=file))
    
    print(bokeh_app)
    print(bokeh_app.handlers[0])
    extra_patterns = [
        ('/upload', UploadHandler)
    ]

    kwargs = {
        'port': port,
    }
    server = Server(bokeh_app,**kwargs, extra_patterns=extra_patterns)

    return server


if __name__ == '__main__':
    # create server
    print('Create server')
    print(os.path.dirname(__file__))
    bokeh_server = create_bokeh_server(file=os.path.dirname(__file__), port=PORT)
    print('Bokeh version: ', bk_ver)
    
    # start bokeh server
    print('Start bokeh server')
    bokeh_server.start()

    # start web server
    print('Start Localhost')
    print(bokeh_server.io_loop)
    bokeh_server.io_loop.start()

upload_handler.py

import uuid
import tornado.web
import json
from time import time

MB = 1024 * 1024
GB = 1024 * MB
MAX_STREAMED_SIZE = 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.request.connection.set_max_body_size(MAX_STREAMED_SIZE)
        self.t0 = time()

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

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

        print(time()-self.t0)
        data = {'filename': fname}
        print(json.dumps(data))

        self.write(json.dumps(data))

main.py

from bokeh.io import curdoc
from bokeh.models import Div
from bkapp import BkApp

def main():
    print('main.main')
    doc = curdoc()
    print(doc)
    a = BkApp.init_var(doc)
    
    
if __name__ == '__main__' or 'bokeh_app_' in __name__:
    main()

bkapp.py

from bokeh.io import curdoc
from bokeh.models import Div


UPLOAD_DIV = '''
    <form id="uploadForm">
        <input type="file" name="file" id="file" />
        <br/>
        <input type="submit" value="Upload" onclick="uploadFn">
    </form>
    <p><span id='display'></span></p>
'''

class BkApp(object):
    def __init__(self, document):
        self.doc = document
        self.div_wdg = Div(text=UPLOAD_DIV)

        self.doc.add_root(self.div_wdg)
        #print('Hello')
        
    @classmethod
    def init_var(cls, document):
        return cls(document)

index.html

{% extends base %}
{% block head %}
<meta charset="UTF-8">
<script src="https://cdn.bokeh.org/bokeh/release/bokeh-2.3.0.min.js"
        crossorigin="anonymous"></script>
<script src="https://cdn.bokeh.org/bokeh/release/bokeh-widgets-2.3.0.min.js"
        crossorigin="anonymous"></script>
<script src="https://cdn.bokeh.org/bokeh/release/bokeh-tables-2.3.0.min.js"
        crossorigin="anonymous"></script>
{% endblock %}
{% block contents %}
    <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>
    async function uploadFn(e) {
    //uploadForm.onsubmit = async (e) => {
        e.preventDefault();
        var fileInput = document.getElementById('file');
        var filename = fileInput.files[0].name;
        console.log(filename);
        
        document.getElementById('display').innerHTML = 
            'Uploading ' + document.getElementById("file").value;

        let response = await fetch(`${window.origin}/upload`, {
            method: "POST",
            body: new FormData(uploadForm)
        });
        let result = await response.json();
        console.log(result);
        document.getElementById('display').innerHTML = 'Finished'
    };
    </script>
    
    {{ embed(roots[0]) }}
{% endblock %}

This topic was automatically closed 90 days after the last reply. New replies are no longer allowed.