Search code examples
python-3.xasynchronoustwistednonblockingklein-mvc

Python Klein - Non Blocking API


Need help with Klein API for Non-blocking. This is a simple test app:

# -*- coding: utf-8 -*-
import datetime
import json
import time
from klein import Klein

app = Klein()

async def delay(seconds):
    """Set some delay for test"""
    time.sleep(seconds)
    return "Works"

@app.route('/', branch=True)
async def main(request):
    some_data = await delay(5)

    return json.dumps([{
        "status": "200",
        "time": datetime.datetime.now().strftime("%Y-%m-%d %H:%M:%S"),
        "data": some_data
    }])

app.run("localhost", 8080)

Then simply run my server.py followed by 2 request at the same time to the http://127.0.0.1:8080/. The results are:

[ { "status": "200", "time": "2019-10-18 20:57:16", "data": "Works" } ]
[ { "status": "200", "time": "2019-10-18 20:57:21", "data": "Works" } ]

5 seconds delay between each response.

Question:

How make this code working with 2 requests at the same time, now it's working one by one...

Also tried to use twistd, results are the same

PYTHONPATH=. twistd --pidfile=apserver.pid -n web --class=api.resource --port tcp:8000:interface=0.0.0.0

Thanks


Solution

  • So, the problem is your delay function - its a blocking function, because python's time.sleep - is blocking. More on this you can read here.

    You should use task.deferLater which you can think of as the non-blocking sleep function of Twisted framework, similar to asyncio.sleep()

    # app.py
    import datetime
    import json
    
    import treq
    from klein import Klein
    from twisted.internet import task, reactor
    
    app = Klein()
    
    async def delay1(secs):
        await task.deferLater(reactor, secs)
        return "Works"
    
    def delay2(secs):
        return task.deferLater(reactor, secs, lambda: "Works")
    
    @app.route('/', branch=True)
    async def main(request):
        some_data = await delay1(5)  # or some_data = await delay2(5) 
    
        return json.dumps([{
            "status": "200",
            "time": datetime.datetime.now().strftime("%Y-%m-%d %H:%M:%S"),
            "data": some_data
        }])
    
    def send_requests():
        for i in range(2):
            print("request", datetime.datetime.now().strftime("%Y-%m-%d %H:%M:%S"))
            d = treq.get('http://localhost:8080')
            d.addCallback(treq.content)
            d.addCallback(lambda x: print("response", x.decode()))
    
    # send 2 concurrent requests every 10 seconds to test the code
    repeating = task.LoopingCall(send_requests)
    repeating.start(10, now=False)
    
    app.run("localhost", 8080)
    

    to run the code

    $ pip3 install treq klein
    $ python3 app.py
    

    wait 10 seconds and the output should be

    2019-10-21 17:17:11-0400 [-] request 2019-10-21 17:17:11
    2019-10-21 17:17:11-0400 [-] request 2019-10-21 17:17:11
    2019-10-21 17:17:16-0400 [-] "127.0.0.1" - - [21/Oct/2019:21:17:16 +0000] "GET / HTTP/1.1" 200 67 "-" "-"
    2019-10-21 17:17:16-0400 [-] "127.0.0.1" - - [21/Oct/2019:21:17:16 +0000] "GET / HTTP/1.1" 200 67 "-" "-"
    2019-10-21 17:17:16-0400 [-] response [{"status": "200", "time": "2019-10-21 17:17:16", "data": "Works"}]
    2019-10-21 17:17:16-0400 [-] response [{"status": "200", "time": "2019-10-21 17:17:16", "data": "Works"}]