带嵌入式Bokeh Server应用程序的Flask中的Code 503通过request.get()获取jsonified数据
我正在通过让Flask应用程序通过专用于通过查询字符串参数传递所请求数据的json路径公开模型数据来对bokeh应用程序进行参数化。我知道数据发送路由有效,因为当我将其用作URL时,AjaxDataSource
可以绘制出所需的数据。但是,当我尝试使用requests.get
api进行等效操作时,我得到了503响应代码,这让我觉得我在这里违反了一些基本知识,我有限的webdev经验无法完全把握住。我在做什么错和/或违反?
实际上,AjaxDataSource
与列限制相比,我需要的数据检索灵活性要比提供的灵活得多。我希望依靠requests
模块来传递任意的类实例,而不会通过序列化和反序列化Json来传递。
这是我演示源自flask_embed.html的失败的最小示例
…
import requests
from flask import Flask, jsonify, render_template
import pandas
from tornado.ioloop import IOLoop
from bokeh.application import Application
from bokeh.application.handlers import FunctionHandler
from bokeh.embed import server_document
from bokeh.layouts import column
from bokeh.models import AjaxDataSource,ColumnDataSource
from bokeh.plotting import figure
from bokeh.server.server import Server
flask_app = Flask(__name__)
# Populate some model maintained by the flask application
modelDf = pandas.DataFrame()
nData = 100
modelDf[ 'c1_x' ] = range(nData)
modelDf[ 'c1_y' ] = [ x*x for x in range(nData) ]
modelDf[ 'c2_x' ] = range(nData)
modelDf[ 'c2_y' ] = [ 2*x for x in range(nData) ]
def modify_doc1(doc):
# get colum name from query string
args = doc.session_context.request.arguments
paramName = str( args['colName'][0].decode('utf-8') )
# get model data from Flask
url = "http://localhost:8080/sendModelData/%s" % paramName
source = AjaxDataSource( data = dict( x=[] , y=[] ) ,
data_url = url ,
polling_interval = 5000 ,
mode = 'replace' ,
method = 'GET' )
# plot the model data
plot = figure( )
plot.circle( 'x' , 'y' , source=source , size=2 )
doc.add_root(column(plot))
def modify_doc2(doc):
# get column name from query string
args = doc.session_context.request.arguments
colName = str( args['colName'][0].decode('utf-8') )
# get model data from Flask
url = "http://localhost:8080/sendModelData/%s" % colName
#pdb.set_trace()
res = requests.get( url , timeout=None , verify=False )
print( "CODE %s" % res.status_code )
print( "ENCODING %s" % res.encoding )
print( "TEXT %s" % res.text )
data = res.json()
# plot the model data
plot = figure()
plot.circle( 'x' , 'y' , source=data , size=2 )
doc.add_root(column(plot))
bokeh_app1 = Application(FunctionHandler(modify_doc1))
bokeh_app2 = Application(FunctionHandler(modify_doc2))
io_loop = IOLoop.current()
server = Server({'/bkapp1': bokeh_app1 , '/bkapp2' : bokeh_app2 }, io_loop=io_loop, allow_websocket_origin=["localhost:8080"])
server.start()
@flask_app.route('/', methods=['GET'] )
def index():
res = "<table>"
res += "<tr><td><a href=\"http://localhost:8080/app1/c1\">APP1 C1</a></td></tr>"
res += "<tr><td><a href=\"http://localhost:8080/app1/c2\">APP1 C2</a></td></tr>"
res += "<tr><td><a href=\"http://localhost:8080/app2/c1\">APP2 C1</a></td></tr>"
res += "<tr><td><a href=\"http://localhost:8080/app2/c2\">APP2 C2</a></td></tr>"
res += "<tr><td><a href=\"http://localhost:8080/sendModelData/c1\">DATA C1</a></td></tr>"
res += "<tr><td><a href=\"http://localhost:8080/sendModelData/c2\">DATA C2</a></td></tr>"
res += "</table>"
return res
@flask_app.route( '/app1/<colName>' , methods=['GET'] )
def bkapp1_page( colName ) :
script = server_document( url='http://localhost:5006/bkapp1' , arguments={'colName' : colName } )
return render_template("embed.html", script=script)
@flask_app.route( '/app2/<colName>' , methods=['GET'] )
def bkapp2_page( colName ) :
script = server_document( url='http://localhost:5006/bkapp2', arguments={'colName' : colName } )
return render_template("embed.html", script=script)
@flask_app.route('/sendModelData/<colName>' , methods=['GET'] )
def sendModelData( colName ) :
x = modelDf[ colName + "_x" ].tolist()
y = modelDf[ colName + "_y" ].tolist()
return jsonify( x=x , y=y )
if __name__ == '__main__':
from tornado.httpserver import HTTPServer
from tornado.wsgi import WSGIContainer
from bokeh.util.browser import view
print('Opening Flask app with embedded Bokeh application on http://localhost:8080/')
# This uses Tornado to server the WSGI app that flask provides. Presumably the IOLoop
# could also be started in a thread, and Flask could server its own app directly
http_server = HTTPServer(WSGIContainer(flask_app))
http_server.listen(8080)
io_loop.add_callback(view, "http://localhost:8080/")
io_loop.start()
这是一些调试输出…
C:\TestApp>python flask_embedJSONRoute.py
Opening Flask app with embedded Bokeh application on http://localhost:8080/
> C:\TestApp\flask_embedjsonroute.py(52)modify_doc2()
-> res = requests.get( url , timeout=None , verify=False )
(Pdb) n
> C:\TestApp\flask_embedjsonroute.py(53)modify_doc2()
-> print( "CODE %s" % res.status_code )
(Pdb) n
CODE 503
> C:\TestApp\flask_embedjsonroute.py(54)modify_doc2()
-> print( "ENCODING %s" % res.encoding )
(Pdb) n
ENCODING utf-8
> C:\TestApp\flask_embedjsonroute.py(55)modify_doc2()
-> print( "TEXT %s" % res.text )
(Pdb) n
TEXT
> C:\TestApp\flask_embedjsonroute.py(56)modify_doc2()
-> data = res.json()
(Pdb)
File "C:\Anaconda3\lib\json\decoder.py", line 357, in raw_decode
raise JSONDecodeError("Expecting value", s, err.value) from None
json.decoder.JSONDecodeError: Expecting value: line 1 column 1 (char 0)
-
这似乎不是Bokeh本身的问题,而是运行Flask应用程序的服务器中的线程和阻塞问题。
除了Bokeh,它完全可以重现…
import requests from flask import Flask, jsonify, request import pandas import pdb flask_app = Flask(__name__) # Populate some model maintained by the flask application modelDf = pandas.DataFrame() nData = 100 modelDf[ 'c1_x' ] = range(nData) modelDf[ 'c1_y' ] = [ x*x for x in range(nData) ] modelDf[ 'c2_x' ] = range(nData) modelDf[ 'c2_y' ] = [ 2*x for x in range(nData) ] @flask_app.route('/', methods=['GET'] ) def index(): res = "<table>" res += "<tr><td><a href=\"http://localhost:8080/sendModelData/c1\">SEND C1</a></td></tr>" res += "<tr><td><a href=\"http://localhost:8080/sendModelData/c2\">SEND C2</a></td></tr>" res += "<tr><td><a href=\"http://localhost:8080/RequestsOverFlaskNoProxy?colName=c1\">REQUEST OVER FLASK NO PROXY C1</a></td></tr>" res += "<tr><td><a href=\"http://localhost:8080/RequestsOverFlaskNoProxy?colName=c2\">REQUEST OVER FLASK NO PROXY C2</a></td></tr>" res += "<tr><td><a href=\"http://localhost:8080/RequestsOverFlask?colName=c1\">REQUEST OVER FLASK C1</a></td></tr>" res += "<tr><td><a href=\"http://localhost:8080/RequestsOverFlask?colName=c2\">REQUEST OVER FLASK C2</a></td></tr>" res += "</table>" return res @flask_app.route('/RequestsOverFlaskNoProxy') def requestsOverFlaskNoProxy() : print("RequestsOverFlaskNoProxy") # get column name from query string colName = request.args.get('colName') # get model data from Flask url = "http://localhost:8080/sendModelData/%s" % colName print("Get data from %s" % url ) session = requests.Session() session.trust_env = False res = session.get( url , timeout=5000 , verify=False ) print( "CODE %s" % res.status_code ) print( "ENCODING %s" % res.encoding ) print( "TEXT %s" % res.text ) data = res.json() return data @flask_app.route('/RequestsOverFlask') def requestsOverFlask() : # get column name from query string colName = request.args.get('colName') # get model data from Flask url = "http://localhost:8080/sendModelData/%s" % colName res = requests.get( url , timeout=None , verify=False ) print( "CODE %s" % res.status_code ) print( "ENCODING %s" % res.encoding ) print( "TEXT %s" % res.text ) data = res.json() return data @flask_app.route('/sendModelData/<colName>' , methods=['GET'] ) def sendModelData( colName ) : x = modelDf[ colName + "_x" ].tolist() y = modelDf[ colName + "_y" ].tolist() return jsonify( x=x , y=y ) if __name__ == '__main__': print('Opening Flask app on http://localhost:8080/') # THIS DOES NOT WORK #flask_app.run( host='0.0.0.0' , port=8080 , debug=True ) # THIS WORKS flask_app.run( host='0.0.0.0' , port=8080 , debug=True , threaded=True )
从屏幕截图中可以看出,直接从
sendModelData
JSon提供数据会适当地呈现JSon,但是通过requests.get
Python控制台中报告的503代码,通过该方法获取时会产生异常。如果我尝试尝试消除通过环境变量启用的代理的影响,但这种方法将永远无法完成,并且请求将使浏览器无限期旋转。
想到它,甚至完全没有必要以中间人的身份使用请求,而且我应该能够只获取json字符串并自行反序列化。好吧,在我的实际代码中,通过在与Flask应用程序完全不同的python模块中完成Bokeh渲染,可以在此设置中起作用,因此除非我加扰应用程序的分层,否则这些功能甚至不可用。
编辑事实证明,我违反的基本原则是Flask的开发环境…
您正在通过Flask测试服务器运行WSGI应用程序,该服务器默认情况下使用单个线程来处理请求。因此,当您的一个请求线程尝试回拨到同一服务器时,它仍在忙于处理该请求。
https://stackoverflow.com/a/22878916/1330381因此,问题就变成了如何在原始Bokeh示例中应用threaded = True技术?flask_embed.py示例对Tornado
WSGI服务器的依赖可能无法实现,从这个问题可以看出Tornado在设计上是单线程的。鉴于上述发现,一个甚至更尖锐的问题是,如何AjaxDataSource
共同避免requests
模块所面临的这些线程问题?
更新 有关散景和龙卷风耦合的更多背景信息…
53:05因此实际上它们不是很多,问题是关于Bokeh和Bokeh服务器的依赖关系。新的Bokeh服务器基于龙卷风构建,这几乎是主要依赖项,因为它使用了龙卷风。除此之外,对于Bokeh而言,没有太多依赖关系(运行时依赖关系)。pandas是Bokeh.charts的可选依赖项。还有其他依赖项,您知道使用了numpy。但是,我认为只有六个或七个依赖项。多年来,我们一直在努力将其削减很多,但是服务器的主要依赖性是龙卷风。Bokeh数据可视化简介-第1部分-
Strata Hadoop San Jose 2016