Servicios Web en Python – Parte 3

En el capítulo anterior, describí la librería SOAPpy, incluyendo su instalación y un ejemplo de cliente y servidor. En este capítulo se explicará el modo de depuración, los «namespace», objetos y parámetros sin nombre y los tipos elaborados.

Modo de depuración

SOAPpy incluye un modo de depuración, que permite tener visibilidad sobre los paquetes XML de entrada y salida.

Para activar este modo basta con, activar un par de banderas:

Proxy.config.dumpSOAPOut = 1
Proxy.config.dumpSOAPIn = 1

La primera bandera indica que queremos mostrar el XML de salida, y la segunda bandera, el XML de entrada.

El siguiente ejemplo muestra como usamos la opción de depuración para que nos muestre el XML de entrada, en el servidor:

#################  SERVIDOR ##################
import SOAPpy

#función que retorna el doble
def doble(x):
    print "petic. atendida"
    return 2*x

server = SOAPpy.SOAPServer(("localhost", 8080))
server.config.dumpSOAPIn = 1 ######## activa depuración
server.registerFunction(doble)
print "Esperando peticiones"
server.serve_forever()

#################  CLIENTE ##################
import SOAPpy
server = SOAPpy.SOAPProxy("http://localhost:8080")
print server.doble(5)

Al ejecutar el cliente, obtendremos, el siguiente mensaje por el lado del servidor:

Esperando peticiones
*** Incoming SOAP ******************************************************
<?xml version="1.0" encoding="UTF-8"?>
<SOAP-ENV:Envelope
  SOAP-ENV:encodingStyle="http://schemas.xmlsoap.org/soap/encoding/"
  xmlns:SOAP-ENC="http://schemas.xmlsoap.org/soap/encoding/"
  xmlns:xsi="http://www.w3.org/1999/XMLSchema-instance"
  xmlns:SOAP-ENV="http://schemas.xmlsoap.org/soap/envelope/"
  xmlns:xsd="http://www.w3.org/1999/XMLSchema"
>
<SOAP-ENV:Body>
<doble SOAP-ENC:root="1">
<v1 xsi:type="xsd:integer">5</v1>
</doble>
</SOAP-ENV:Body>
</SOAP-ENV:Envelope>
************************************************************************
petic. atendida

El XML de entrada, se muestra completamente en pantalla. Esta opción es útil para saber, que es lo que está llegando realmente al servidor. Las llamadas erróneas a funciones o a sus parámetros, se mostrarán en pantalla para poder corregirlas.

Manejando los “namespace”

Una función remota, puede pertenecer a un módulo o librería. De esta forma se pueden agrupar diversas funciones en diversos módulos.

A esta librería o módulo, se le llama “namespace”, en la terminología de SOAP.

Para exponer una función con un “namespace” específico, se debe registrar la función indicando el nombre del “namespace”:

#################  SERVIDOR ##################
from SOAPpy import SOAPServer

def doble(s):
    print "petic. atendida"
    return 2*s  
    
server = SOAPServer(("localhost", 8080))
server.registerFunction(doble, "mi-espacio")
print "Esperando peticiones"
server.serve_forever()

#################  CLIENTE ##################
import SOAPpy
server = SOAPpy.SOAPProxy("http://localhost:8080/",namespace = "mi-espacio")
print server.doble(2)

El nombre del “namespace” requerido, se especifica en al usar el método SOAPProxy() en el lado del cliente. Este nombre no debe contener espacios, pero si puede contener símbolos.

Otra forma de implementar el cliente sería:

#################  CLIENTE ##################
import SOAPpy
server = SOAPpy.SOAPProxy("http://localhost:8080/")
print server._ns("mi-espacio").doble(2)

Esta forma de llamado nos permite también, cambiar el alias que se usa para referenciar el “namespace” en el XML:

#################  CLIENTE ##################
import SOAPpy
server = SOAPpy.SOAPProxy("http://localhost:8080/")
print server._ns("miesp", "mi-espacio").doble(2)

Con este código, al generarse el XML request, se usará el nombre “miesp” en lugar del nombre por defecto “ns1”.

Usando objetos y parámetros sin nombre

En este caso hemos usado el método “_ns” para especificar el “namespace” del objeto “server”. El siguiente ejemplo registrar todos los métodos de un objeto:

#################  SERVIDOR ##################
from SOAPpy import SOAPServer

class miClase:
    def doble(self, s):
        print "petic. atendida"
        return 2*s  
    
server = SOAPServer(("localhost", 8080))
e = miClase()
server.registerObject(e)
print "Esperando peticiones"
server.serve_forever()

La llamada a “doble” es como si se tratara de cualquier función expuesta:

#################  CLIENTE ##################
import SOAPpy
server = SOAPpy.SOAPProxy("http://localhost:8080/")
print server.doble(1)

También se puede hacer una llamada a un procedimiento remoto, especificando el nombre de la variable: print server.doble(s = 1).

Pero si quisiéramos usar argumentos sin nombre de tipo (**parametros) en el lado del servidor, debemos usar la función server.registerKWFunction():

#################  SERVIDOR ##################
import SOAPpy

#función que retorna el doble
def doble(**lista):
    print "petic. atendida"
    return lista['primero'] + lista['segundo']

server = SOAPpy.SOAPServer(("localhost", 8080))
server.registerKWFunction(doble)
print "Esperando peticiones"
server.serve_forever()

#################  CLIENTE ##################
import SOAPpy
server = SOAPpy.SOAPProxy("http://localhost:8080/")
print server.doble(primero = 1, segundo = 2)

Todos los parámetros usados en la función remota, deben ser utilizados. Cualquier error en los nombres de los parámetros generará un error en el servidor y en el cliente.

Tipos elaborados

Hasta ahora, los ejemplos, de funciones remotas, has sido con parámetros de tipo numérico o cadena. Sin embargo es posible usar parámetros y resultados más complejos.

Consideremos el siguiente ejemplo de servidor:

#################  SERVIDOR ##################
from SOAPpy import SOAPServer

def Mostrar(miEstruc):
    myfloat = miEstruc["Flotante"]
    myint = miEstruc["Entero"]
    mystr = miEstruc["Cadena"]
    return miEstruc
    
server = SOAPServer(("localhost", 8080))
server.registerFunction(Mostrar)
server.serve_forever()

Aquí exponemos una función que recibe un parámetro de tipo diccionario.  El cliente puede implementarse así:

#################  CLIENTE ##################
import SOAPpy

dicionario = {'Flotante': 3.1415, 'Entero': 123, 'Cadena': 'Abc'}

server = SOAPpy.SOAPProxy("http://localhost:8080/")
print server.Mostrar(miEstruc = dicionario)

A bajo nivel, los diccionarios de Python se convierten en el formato XML que corresponde. El XML request  sería de la forma:

*** Incoming SOAP ******************************************************
<?xml version="1.0" encoding="UTF-8"?>
<SOAP-ENV:Envelope
  SOAP-ENV:encodingStyle="http://schemas.xmlsoap.org/soap/encoding/"
  xmlns:SOAP-ENC="http://schemas.xmlsoap.org/soap/encoding/"
  xmlns:xsi="http://www.w3.org/1999/XMLSchema-instance"
  xmlns:SOAP-ENV="http://schemas.xmlsoap.org/soap/envelope/"
  xmlns:xsd="http://www.w3.org/1999/XMLSchema"
>
<SOAP-ENV:Body>
<Mostrar SOAP-ENC:root="1">
<miEstruc>
<Cadena xsi:type="xsd:string">Abc</Cadena>
<Entero xsi:type="xsd:integer">123</Entero>
<Flotante xsi:type="xsd:double">3.1415</Flotante>
</miEstruc>
</Mostrar>
</SOAP-ENV:Body>
</SOAP-ENV:Envelope>
************************************************************************

En el cliente, se puede prescindir de los diccionarios y crear estructuras similares usando structType():

Estruc = structType(name='miEstruc', typed=0)
Estruc._addItem('Flotante', 3.1415)
Estruc._addItem('Entero', 123)
Estruc._addItem('Cadena', 'Abc')

Así podemos definir un nuevo cliente:

#################  CLIENTE ##################
import SOAPpy

dicionario = {'Flotante': 3.1415, 'Entero': 123, 'Cadena': 'Abc'}

server = SOAPpy.SOAPProxy("http://localhost:8080/")

Estruc = SOAPpy.structType(name='miEstruc', typed=0)
Estruc._addItem('Flotante', 3.1415)
Estruc._addItem('Entero', 123)
Estruc._addItem('Cadena', 'Abc')

x = SOAPpy.buildSOAP(Estruc, method="miEstruc", namespace="")
print server.Mostrar(miEstruc = dicionario)

El resultado sería exactamente el mismo. Observar el uso del método “buildSOAP” para pasar la nueva estructura.

Se puede encontrar más información sobre este tema en:

https://github.com/kiorky/SOAPpy/blob/develop/docs/simpleTypes.txt

https://github.com/kiorky/SOAPpy/blob/develop/docs/complexTypes.txt

Obtener información del lado remoto

Es posible obtener información de lado  remoto usando SOAPpy.

Este sencillo ejemplo muestra como devolver la IP del cliente:

#################  SERVIDOR ##################
from SOAPpy import *

def Saludo(_SOAPContext = None):
    print "La IP origen es %s" % _SOAPContext.connection.getpeername()[0]
    return "Ya tengo tu IP"

server = SOAPServer(("localhost", 8080 ))
server.registerFunction(MethodSig(Saludo, keywords=0, context=1) )
print "Esperando peticiones"
server.serve_forever()

#################  CLIENTE ##################
import SOAPpy
server = SOAPpy.SOAPProxy("http://localhost:8080")
print server.Saludo()

Observar que a diferencia de los ejemplos anteriores, aquí se requiere llamar a server.registerFunction() usando la función MethodSig().

Existe más información adicional que se puede obtener del lado remoto, la mayoría de ella se obtiene de las cabeceras:

#################  SERVIDOR ##################
import SOAPpy

#Retorna el doble
def doble(x, _SOAPContext):
    c = _SOAPContext
    print "=== XML request ==="
    print c.xmldata
    print "=== encabezado ==="
    print c.header
    print "=== cuerpo ==="
    print c.body
    print "=== Socket Object ==="
    print c.connection.getpeername()
    print "=== SOAPaction HTTP header ==="
    print c.soapaction
    print "=== HTTp headers ==="
    print c.httpheaders
    print "petic. atendida"
    return 2*x

server = SOAPpy.SOAPServer(("localhost", 8080))
server.registerFunction(SOAPpy.MethodSig(doble, keywords=0,context=1))
print "Esperando peticiones"
server.serve_forever()


#################  CLIENTE ##################
import SOAPpy
server = SOAPpy.SOAPProxy("http://localhost:8080")
print server.doble(5)

El elemento “header” (en caso existiera) puede ser leído también, accediendo a uno de sus campos. Por ejemplo, si el encabezado tuviera la propiedad “campo1”, la lectura se haría así:

	print c.header.campo1

El elemento “connection” puede retornar también la IP de esta forma:

	print c.connection.get_remote_address() 

Existe un muy buen ejemplo de un servidor que accede a esta información, en:

https://github.com/tarequeh/SOAPpy/blob/master/tests/echoServer.py

Con este ejemplo, concluyo esta entrega. En el siguiente capítulo abordaré el tema de los archivos WSDL.


Sé el primero en comentar

Dejar una contestacion

Tu dirección de correo electrónico no será publicada.


*