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 для любого в будущем с похожими вопросами...
Ответы
Ответ 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
), поэтому я предлагаю вам придерживаться этих правил и избегать диспетчера.