python - Twisted XmlStream: ¿Cómo conectarse a eventos?
events network-programming (2)
El "único" componente relevante de XmlStream
es el analizador de SAX. Aquí es cómo he implementado un analizador asíncrono SAX usando XmlStream
y solo las funciones de análisis 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())
Luego crea una clase de Fábrica que producirá este Protocolo (del que ha demostrado que es capaz). Básicamente, obtendrá toda su información del XML en las funciones onDocumentStart
y onElement
y cuando llegue al final (es decir, onDocumentEnd
) enviará una respuesta basada en la información analizada. Además, asegúrese de llamar a self._initializestream()
después de analizar cada mensaje XML o de lo contrario obtendrá una excepción. Eso debería servir como un buen esqueleto para ti.
Mis respuestas a tus preguntas:
- No se :)
- Es muy razonable Sin embargo, normalmente solo
XmlStream
subclase deXmlStream
(que simplemente se hereda delProtocol
) y luego uso un objetoFactory
normal. - Esto es algo de lo que debe preocuparse cuando use Twisted (+1 para usted). Usando el enfoque anterior, puede disparar devoluciones de llamada / errbacks mientras analiza y pulsa un elemento o esperar hasta llegar al final del XML y luego disparar sus devoluciones de llamada a su corazón. Espero que tenga sentido :/
- Me he preguntado esto también en realidad. Creo que tiene algo que ver con las aplicaciones y los protocolos que utilizan el objeto
XmlStream
(como Jabber e IRC). Solo sobrecargueonDocumentEnd
y haga que haga lo que usted quiere que haga. Esa es la belleza de OOP.
Referencia:
- xml.sax
- iterparse
- twisted.web.sux : analizador XML SAX trenzado. Esto es realmente lo que
XmlStream
usa para analizar XML. - iterparse : Aquí hay otra pregunta de Stackoverflow: la estrategia iterparse de ElementTree
- iterparse está lanzando ''no se ha encontrado ningún elemento: línea 1, columna 0'' y no estoy seguro de por qué . He hecho una pregunta similar :)
PD
Su problema es bastante común y muy simple de resolver (al menos en mi opinión), así que no se suicide al tratar de aprender el modelo Event Dipatcher. En realidad, parece que tienes un buen manejo de devoluciones de llamada y errbacks (también conocido como Deferred
), por lo que te sugiero que te limites a ellos y evites al despachador.
Me gustaría implementar un servidor Twisted que espere solicitudes XML y envíe respuestas XML a cambio:
<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>
He creado un cliente y un servidor Twisted before que se intercambiaran cadenas simples y traté de extenderlo al uso de XML, pero parece que no puedo encontrar la manera de configurarlo todo correctamente.
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()
Como se mencionó en el enfoque anterior basado en cadenas, el cliente permanece inactivo hasta CTRL + C. Una vez que lo haga, se inspirará / inspirará en el ejemplo de 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()
Salida de 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>
Salida de server.py
:
onConnected(...)
onRequest(...)
onDisconnected(...)
Mis preguntas:
- ¿Cómo me suscribo a un evento activado cuando el servidor encuentra una determinada etiqueta XML? La consulta de
//request/content
XPath me parece bien, peroonRequestContent(...)
no se llama :-( - ¿La subclasificación de
XmlStream
yXmlStreamFactory
un enfoque razonable? Se siente raro porqueXMLServer
suscribe a eventos enviados por su propia clase base y luego se pasa a sí mismo (?) Como parámetroxs
?!? ¿Debería hacer queXMLServer
una clase normal y tener un objetoXmlStream
como miembro de la clase? ¿Hay un enfoque canónico ? - ¿Cómo agregaría un controlador de errores al servidor como
addErrback(...)
en el cliente? Me preocupa que las excepciones sean tragadas (sucedieron antes), pero no veo de dónde obtener unDeferred
para adjuntarlo a ... - ¿Por qué el servidor cierra la conexión por defecto después de la primera solicitud? Veo
XmlStream.onDocumentEnd(...)
llamando aloseConnection()
; Podría anular ese método, pero me pregunto si hay una razón para el cierre que no veo. ¿No es el enfoque "normal" dejar la conexión abierta hasta que se haya realizado toda la comunicación necesaria hasta el momento?
Espero que esta publicación no sea considerada demasiado específica; hablar de XML a través de la red es un lugar común, pero a pesar de buscar un día y medio, no pude encontrar ningún ejemplo de servidor XML retorcido. Tal vez logre convertir esto en un punto de partida para cualquier persona en el futuro con preguntas similares ...
Esto es principalmente una suposición, pero por lo que sé, necesita abrir la transmisión enviando una estrofa sin cerrarla.
En su ejemplo, cuando envía <request type=''type 01''><content>some request content</content></request>
el servidor ve la stanza <request>
como el start document
pero luego envía </request>
y El servidor lo verá como el end document
.
Básicamente, su servidor consume <request>
como documento de inicio y por eso su xpath, //request/content
, no coincidirá, porque todo lo que queda del elemento es <content>...</content>
.
Intente enviar algo como <stream>
desde el cliente primero, luego las dos solicitudes y luego </stream>
.
Además, la subclasificación de XmlStream
está bien siempre y cuando se asegure de no anular ningún método de forma predeterminada.