带嵌入式Bokeh Server应用程序的Flask中的Code 503通过request.get()获取jsonified数据

发布于 2021-01-29 16:43:42

我正在通过让Flask应用程序通过专用于通过查询字符串参数传递所请求数据的json路径公开模型数据来对bokeh应用程序进行参数化。我知道数据发送路由有效,因为当我将其用作URL时,AjaxDataSource可以绘制出所需的数据。但是,当我尝试使用requests.getapi进行等效操作时,我得到了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()

这是呈现的页面… Flask Model
json检索工作与不工作的比较

这是一些调试输出…

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)
关注者
0
被浏览
246
1 个回答
  • 面试哥
    面试哥 2021-01-29
    为面试而生,有面试问题,就找面试哥。

    这似乎不是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 )
    

    提供相同数据的不同行为

    从屏幕截图中可以看出,直接从sendModelDataJSon提供数据会适当地呈现JSon,但是通过requests.getPython控制台中报告的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



知识点
面圈网VIP题库

面圈网VIP题库全新上线,海量真题题库资源。 90大类考试,超10万份考试真题开放下载啦

去下载看看