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 %}