Подтвердить что ты не робот

Twisted XmlStream: Как подключиться к событиям?

Я хотел бы реализовать Twisted-сервер, который ожидает запросов XML и отправляет ответы XML в ответ:

<request type='type 01'><content>some request content</content></request>
<response type='type 01'><content>some response content</content></response>
<request type='type 02'><content>other request content</content></request>
<response type='type 02'><content>other response content</content></response>

Я создал Twisted client и server до, которые обменивались простыми строками и пытались распространить это на использование XML, но я не могу понять, как правильно настройте его.

client.py:

#!/usr/bin/env python
# encoding: utf-8

from twisted.internet             import reactor
from twisted.internet.endpoints   import TCP4ClientEndpoint, connectProtocol
from twisted.words.xish.domish    import Element, IElement
from twisted.words.xish.xmlstream import XmlStream

class XMLClient(XmlStream):

    def sendObject(self, obj):
        if IElement.providedBy(obj):
            print "[TX]: %s" % obj.toXml()
        else:
            print "[TX]: %s" % obj
        self.send(obj)

def gotProtocol(p):
    request = Element((None, 'request'))
    request['type'] = 'type 01'
    request.addElement('content').addContent('some request content')
    p.sendObject(request)

    request = Element((None, 'request'))
    request['type'] = 'type 02'
    request.addElement('content').addContent('other request content')

    reactor.callLater(1, p.sendObject, request)
    reactor.callLater(2, p.transport.loseConnection)

endpoint = TCP4ClientEndpoint(reactor, '127.0.0.1', 12345)
d = connectProtocol(endpoint, XMLClient())
d.addCallback(gotProtocol)

from twisted.python import log
d.addErrback(log.err)

reactor.run()

Как и в предыдущем основанном на строках подходе, клиент простаивает до CTRL + C. Как только я это сделаю, он привлечет некоторое/много вдохновения из примера Twisted XMPP.

server.py:

#!/usr/bin/env python
# encoding: utf-8

from twisted.internet             import reactor
from twisted.internet.endpoints   import TCP4ServerEndpoint
from twisted.words.xish.xmlstream import XmlStream, XmlStreamFactory
from twisted.words.xish.xmlstream import STREAM_CONNECTED_EVENT, STREAM_START_EVENT, STREAM_END_EVENT

REQUEST_CONTENT_EVENT = intern("//request/content")

class XMLServer(XmlStream):
    def __init__(self):
        XmlStream.__init__(self)
        self.addObserver(STREAM_CONNECTED_EVENT, self.onConnected)
        self.addObserver(STREAM_START_EVENT,     self.onRequest)
        self.addObserver(STREAM_END_EVENT,       self.onDisconnected)
        self.addObserver(REQUEST_CONTENT_EVENT,  self.onRequestContent)

    def onConnected(self, xs):
        print 'onConnected(...)'

    def onDisconnected(self, xs):
        print 'onDisconnected(...)'

    def onRequest(self, xs):
        print 'onRequest(...)'

    def onRequestContent(self, xs):
        print 'onRequestContent(...)'

class XMLServerFactory(XmlStreamFactory):
    protocol = XMLServer

endpoint = TCP4ServerEndpoint(reactor, 12345, interface='127.0.0.1')
endpoint.listen(XMLServerFactory())
reactor.run()

client.py вывод:

TX [127.0.0.1]: <request type='type 01'><content>some request content</content></request>
TX [127.0.0.1]: <request type='type 02'><content>other request content</content></request>

server.py вывод:

onConnected(...)
onRequest(...)
onDisconnected(...)

Мои вопросы:

  • Как подписаться на событие, запущенное, когда сервер сталкивается с определенным тегом XML? Запрос //request/content XPath кажется мне удобным, но onRequestContent(...) не вызван: - (
  • Является ли подклассом XmlStream и XmlStreamFactory разумным подходом вообще? Чувство странно, потому что XMLServer подписывается на события, отправленные его собственным базовым классом, а затем передается сам (?) Как xs параметр?!? Должен ли я сделать XMLServer обычный класс и иметь объект XmlStream как член класса? Существует ли канонический подход?
  • Как добавить обработчик ошибок на сервер, например addErrback(...) в клиенте? Я беспокоюсь, что исключения проглатываются (раньше было), но я не вижу, где получить Deferred, чтобы прикрепить его к...
  • Почему сервер по умолчанию закрывает соединение после первого запроса? Я вижу XmlStream.onDocumentEnd(...) вызов loseConnection(); Я мог бы переопределить этот метод, но мне интересно, есть ли причина закрытия, которую я не вижу. Разве это не "нормальный" подход, чтобы оставить соединение открытым до тех пор, пока не будет выполнено все необходимые коммуникации на данный момент?

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

4b9b3361

Ответ 1

Это в основном догадка, но насколько я знаю, вам нужно открыть поток, отправив строфу, не закрывая ее.

В вашем примере, когда вы отправляете <request type='type 01'><content>some request content</content></request>, сервер видит строку <request> как start document, но затем вы отправляете </request>, и сервер увидит это как end document.

В принципе, ваш сервер потребляет <request> в качестве исходного документа, а также почему ваш xpath, //request/content не будет соответствовать, потому что все, что осталось от элемента, <content>...</content>.

Попробуйте сначала отправить что-то вроде <stream> из клиента, затем два запроса, а затем </stream>.

Кроме того, подклассификация XmlStream прекрасна до тех пор, пока вы не будете переопределять какие-либо методы по умолчанию.

Ответ 2

"Единственным" релевантным компонентом XmlStream является синтаксический анализатор SAX. Здесь, как я реализовал асинхронный SAX-анализатор, используя XmlStream и только функции синтаксического анализа XML:

server.py

from twisted.words.xish.domish import Element
from twisted.words.xish.xmlstream import XmlStream

class XmlServer(XmlStream):
    def __init__(self):
        XmlStream.__init__(self)    # possibly unnecessary

    def dataReceived(self, data):
        """ Overload this function to simply pass the incoming data into the XML parser """
        try:
            self.stream.parse(data)     # self.stream gets created after self._initializestream() is called
        except Exception as e:
            self._initializeStream()    # reinit the DOM so other XML can be parsed

    def onDocumentStart(self, elementRoot):
        """ The root tag has been parsed """
        print('Root tag: {0}'.format(elementRoot.name))
        print('Attributes: {0}'.format(elementRoot.attributes))

    def onElement(self, element):
        """ Children/Body elements parsed """
        print('\nElement tag: {0}'.format(element.name))
        print('Element attributes: {0}'.format(element.attributes))
        print('Element content: {0}'.format(str(element)))

    def onDocumentEnd(self):
        """ Parsing has finished, you should send your response now """
        response = domish.Element(('', 'response'))
        response['type'] = 'type 01'
        response.addElement('content', content='some response content')
        self.send(response.toXml())

Затем вы создаете класс Factory, который будет генерировать этот протокол (который вы продемонстрировали, на который вы способны). В принципе, вы получите всю информацию из XML в функциях onDocumentStart и onElement, и когда вы достигнете конца (т.е. onDocumentEnd), вы отправите ответ на основе анализируемой информации. Кроме того, не забудьте вызывать self._initializestream() после разбора каждого XML-сообщения, иначе вы получите исключение. Это должно служить хорошим скелетом для вас.

Мои ответы на ваши вопросы:

  • Не знаю:)
  • Это очень разумно. Однако я обычно просто подклассом XmlStream (который просто наследуется от Protocol), а затем использует обычный объект Factory.
  • Это хорошая вещь, о которой нужно беспокоиться при использовании Twisted (+1 для вас). Используя вышеприведенный подход, вы можете запускать обратные вызовы /errbacks, когда вы анализируете и ударяете элемент, или ждите, пока не дойдете до конца XML, а затем вызовите свои обратные вызовы в ваше сердце. Надеюсь, это имеет смысл:/
  • Я тоже подумал об этом. Я думаю, что это имеет какое-то отношение к приложениям и протоколам, которые используют объект XmlStream (например, Jabber и IRC). Просто перегрузите onDocumentEnd и сделайте так, как хотите. Это красота ООП.

Справка:

PS

Ваша проблема довольно распространена и очень проста для решения (по крайней мере, на мой взгляд), поэтому не убивайте себя, пытаясь изучить модель Dipatcher Event. На самом деле, похоже, вы хорошо справляетесь с обратными вызовами и ошибками (aka Deferred), поэтому я предлагаю вам придерживаться этих правил и избегать диспетчера.