Twisted Klein: синхронное поведение

Я использую Twisted Klein, потому что одно из обещаний фреймворка - это асинхронность, но я протестировал это приложение, которое я разрабатываю, и немного кода для тестирования, и поведение фреймворка кажется синхронным.

Код тестового сервера:

# -*- encoding: utf-8 -*-
import json
import time
from datetime import datetime

from klein import Klein

app = Klein()

def setHeader(request, content_type):

    request.setHeader('Access-Control-Allow-Origin', '*')
    request.setHeader('Access-Control-Allow-Methods', 'GET')
    request.setHeader('Access-Control-Allow-Headers', 'x-prototype-version,x-requested-with')
    request.setHeader('Access-Control-Max-Age', 2520)
    request.setHeader('Content-type', content_type)


def cleanParams(params):

    for key in params.keys():

        param = params[key]
        params[key] = param[0]

    return params


@app.route('/test/', methods=["GET"])
def test(request):

    setHeader(request,'application/json')

    time.sleep(5)

    return json.dumps([str(datetime.now())])

if __name__ == "__main__":
    app.run(host='0.0.0.0',port=12030)

И запрос на тестирование:

# -*- encoding: utf-8 -*-
import requests
from datetime import datetime

if __name__ == "__main__":

    url = "http://192.168.50.205:12030"

    params = {
    }

    print datetime.now()
    for i in xrange(6):
        result = requests.get(url + "/test/", params)

        print datetime.now(), result.json()

С сервером, если я запускаю второй код один:

2016-07-19 12:50:53.530000
2016-07-19 12:50:58.570000 [u'2016-07-19 12:50:58.548000']
2016-07-19 12:51:03.604000 [u'2016-07-19 12:51:03.589000']
2016-07-19 12:51:08.634000 [u'2016-07-19 12:51:08.625000']
2016-07-19 12:51:13.670000 [u'2016-07-19 12:51:13.654000']
2016-07-19 12:51:18.717000 [u'2016-07-19 12:51:18.708000']
2016-07-19 12:51:23.764000 [u'2016-07-19 12:51:23.748000']

Отлично, но если я бегу одновременно два экземпляра:

Экземпляр 1:

2016-07-19 12:53:05.025000
2016-07-19 12:53:10.057000 [u'2016-07-19 12:53:10.042000']
2016-07-19 12:53:20.113000 [u'2016-07-19 12:53:20.097000']
2016-07-19 12:53:30.181000 [u'2016-07-19 12:53:30.166000']
2016-07-19 12:53:40.236000 [u'2016-07-19 12:53:40.219000']
2016-07-19 12:53:50.316000 [u'2016-07-19 12:53:50.294000']
2016-07-19 12:54:00.381000 [u'2016-07-19 12:54:00.366000']

Экземпляр 2:

2016-07-19 12:53:05.282000
2016-07-19 12:53:15.074000 [u'2016-07-19 12:53:15.059000']
2016-07-19 12:53:25.141000 [u'2016-07-19 12:53:25.125000']
2016-07-19 12:53:35.214000 [u'2016-07-19 12:53:35.210000']
2016-07-19 12:53:45.270000 [u'2016-07-19 12:53:45.255000']
2016-07-19 12:53:55.362000 [u'2016-07-19 12:53:55.346000']
2016-07-19 12:54:05.402000 [u'2016-07-19 12:54:05.387000']

И вывод сервера:

2016-07-19 12:53:10-0400 [-] "192.168.50.205" - - [19/Jul/2016:16:53:04 +0000] "GET /test/ HTTP/1.1" 200 30 "-" "python-requests/2.9.1"
2016-07-19 12:53:15-0400 [-] "192.168.50.205" - - [19/Jul/2016:16:53:10 +0000] "GET /test/ HTTP/1.1" 200 30 "-" "python-requests/2.9.1"
2016-07-19 12:53:20-0400 [-] "192.168.50.205" - - [19/Jul/2016:16:53:15 +0000] "GET /test/ HTTP/1.1" 200 30 "-" "python-requests/2.9.1"
2016-07-19 12:53:25-0400 [-] "192.168.50.205" - - [19/Jul/2016:16:53:20 +0000] "GET /test/ HTTP/1.1" 200 30 "-" "python-requests/2.9.1"
2016-07-19 12:53:30-0400 [-] "192.168.50.205" - - [19/Jul/2016:16:53:25 +0000] "GET /test/ HTTP/1.1" 200 30 "-" "python-requests/2.9.1"
2016-07-19 12:53:35-0400 [-] "192.168.50.205" - - [19/Jul/2016:16:53:30 +0000] "GET /test/ HTTP/1.1" 200 30 "-" "python-requests/2.9.1"
2016-07-19 12:53:40-0400 [-] "192.168.50.205" - - [19/Jul/2016:16:53:35 +0000] "GET /test/ HTTP/1.1" 200 30 "-" "python-requests/2.9.1"
2016-07-19 12:53:45-0400 [-] "192.168.50.205" - - [19/Jul/2016:16:53:40 +0000] "GET /test/ HTTP/1.1" 200 30 "-" "python-requests/2.9.1"
2016-07-19 12:53:50-0400 [-] "192.168.50.205" - - [19/Jul/2016:16:53:45 +0000] "GET /test/ HTTP/1.1" 200 30 "-" "python-requests/2.9.1"
2016-07-19 12:53:55-0400 [-] "192.168.50.205" - - [19/Jul/2016:16:53:50 +0000] "GET /test/ HTTP/1.1" 200 30 "-" "python-requests/2.9.1"
2016-07-19 12:54:00-0400 [-] "192.168.50.205" - - [19/Jul/2016:16:53:55 +0000] "GET /test/ HTTP/1.1" 200 30 "-" "python-requests/2.9.1"
2016-07-19 12:54:05-0400 [-] "192.168.50.205" - - [19/Jul/2016:16:54:00 +0000] "GET /test/ HTTP/1.1" 200 30 "-" "python-requests/2.9.1"

Как видите, сервер блокирует текущее выполнение, и он работает синхронно, а не асинхронно.

Что мне не хватает?

С уважением.

1 ответ

Вам не хватает многих важных концепций Twisted. Что касается синхронного поведения, вы абсолютно правы, Klein ведет себя как синхронные фреймворки, такие как Flask или Bottle, если вы явно не используете асинхронные функции (т.е. Deferreds). В вашем примере вы не используете никаких асинхронных функций, поэтому ваш код выполняется последовательно. Проверьте https://github.com/notoriousno/klein-basics/blob/intro/nonblocking.rst это должно помочь вам понять основы асинхронности в Klein и Twisted. Как напоминание читателям, Deferreds не делает ваш код магически асинхронным! Вы должны тщательно спроектировать, чтобы добиться одновременного выполнения.

Делаем ваш код асинхронным

Попробуем исправить ваш код так, чтобы он работал асинхронно. Я пойду по понятиям в разделах. Если вам нужна дополнительная информация, пожалуйста, прокомментируйте, и я ее рассмотрю. Давайте начнем с необходимого импорта:

from klein import Klein
from twisted.internet import defer, reactor

SetHeader()

Далее давайте посмотрим, чтобы изменить setHeader() функция. request.setHeader Функция довольно быстрая, поэтому ее можно запускать несколько раз без серьезной блокировки. Следовательно, функция, которая генерирует Deferred Можно использовать объект с обратными вызовами, которые будут устанавливать различные пары ключ / значение заголовка:

def setHeader(request, content_type):

    def _setHeader(previous_result, header, value):
        request.setHeader(header, value)

    d = defer.Deferred()
    d.addCallback(_setHeader, 'Access-Control-Allow-Origin', '*')
    d.addCallback(_setHeader, 'Access-Control-Allow-Methods', 'GET')
    d.addCallback(_setHeader, 'Access-Control-Allow-Headers', 'x-prototype-version,x-requested-with')
    d.addCallback(_setHeader, 'Access-Control-Max-Age', '2520')
    d.addCallback(_setHeader, 'Content-type', content_type)
    return d

Не вдаваясь в подробности, мы используем Deferred.addCallback() связать обратные вызовы вместе. В этом случае функция обратного вызова является локальной _setHeader() и это просто устанавливает заголовок. Наконец, функция вернет Deferred, Если вы заметили, _setHeader() принимает аргумент previous_result давайте пока проигнорируем их.

cleanParams ()

Если используется цикл (for или же while) это вообще лучше использовать inlineCallbacks в yield Результаты. Использование этого метода позволяет запускать вещи синхронно, не блокируя основной ioloop.

@defer.inlineCallbacks
def cleanParams(params):
    for key in sorted(params):
        param = params[key]
        params[key] = yield param[0]

    defer.returnValue(str(params))    # if py3 then use ``return params``

Это плохой пример, но он должен иллюстрировать, как использовать yield ждать значения. Как примечание стороны, setHeader() функция могла бы также использовать inlineCallbacks а также yields, Я хотел продемонстрировать несколько асинхронных стилей.

Кляйн Маршруты

Наконец, давайте фактически используем асинхронные функции в маршрутах:

app = Klein()

@app.route('/test/', methods=["GET"])
def test(request):
    asyncClean = cleanParams(request.args)
    asyncClean.addCallback(request.write)

    asyncSetHeader = setHeader(request,'application/json')
    reactor.callLater(5, asyncSetHeader.callback, None)

    def render(results, req):
        req.write(json.dumps([str(datetime.now())]))

    finalResults = defer.gatherResults([asyncClean, asyncSetHeader])
    finalResults.addCallback(render, request)
    return finalResults

НЕ ВЫБИРАЙТЕСЬ! Сначала мы позвоним cleanParams() который возвращает Deferred и когда это заканчивается, returnValue будет написано в теле ответа. Далее заголовки будут установлены через наш setHeader() который явно возвращает Deferred, Вы использовали time.sleep(5) и вы случайно заблокировали весь цикл реактора. В Klein/Twisted вы обычно используете callLater() если вы хотите что-то сделать позже. Наконец мы ждем результатов от отложенных asyncClean а также asyncSetHeader с помощью gatherResults() и запишите метку времени в тело ответа.

Окончательный код

server.py

import json
from datetime import datetime

from klein import Klein
from twisted.internet import defer, reactor


app = Klein()

def setHeader(request, content_type):

    def _setHeader(previous_result, header, value):
        request.setHeader(header, value)

    d = defer.Deferred()
    d.addCallback(_setHeader, 'Access-Control-Allow-Origin', '*')
    d.addCallback(_setHeader, 'Access-Control-Allow-Methods', 'GET')
    d.addCallback(_setHeader, 'Access-Control-Allow-Headers', 'x-prototype-version,x-requested-with')
    d.addCallback(_setHeader, 'Access-Control-Max-Age', '2520')
    d.addCallback(_setHeader, 'Content-type', content_type)
    return d


@defer.inlineCallbacks
def cleanParams(params):
    for key in sorted(params):
        param = params[key]
        params[key] = yield param[0]

    defer.returnValue(str(params))


@app.route('/test/', methods=["GET"])
def test(request):
    asyncClean = cleanParams(request.args)
    asyncClean.addCallback(request.write)       # write the result from cleanParams() to the response

    asyncSetHeader = setHeader(request,'application/json')
    reactor.callLater(5, asyncSetHeader.callback, None)

    def render(results, req):
        req.write(json.dumps([str(datetime.now())]))

    finalResults = defer.gatherResults([asyncClean, asyncSetHeader])
    finalResults.addCallback(render, request)
    return finalResults

if __name__ == "__main__":
    app.run(host='0.0.0.0',port=12030)

test.sh

curl -v -X GET http://localhost:12030/test/?hello=world\&foo=bar\&fizz=buzz
Другие вопросы по тегам