miércoles, 15 de agosto de 2012

Consumir un Web Service desde Oracle

Hace algún tiempo tuve la oportunidad (a petición de mi jefe) de investigar una forma de consumir un servicio web elaborado con .NET desde un procedimiento o función de Oracle.

Como apenas estaba conociendo esta base de datos y el tan interesante PL/SQL, imaginé que sería algo difícil de conseguir, pero eso es lo que me gusta, así que acepté el reto.

Busqué y encontré varias formas, pero definitivamente lo que más me convenció fué un paquete de oracle llamado SOAP API.

http://www.oracle-base.com/dba/script.php?category=miscellaneous&file=soap_api.sql

Con este paquete, se puede crear una función o procedimiento con el cual se pueda invocar el servicio web; tenemos un ejemplo del uso de este paquete en el siguiente enlace:

http://www.oracle-base.com/articles/9i/consuming-web-services-9i.php

Ahora bien, a veces lo que cuesta con esto es comprender qué es cada cosa, así que vamos en orden.


CREATE OR REPLACE FUNCTION add_numbers (p_int_1  IN  NUMBER,
                                        p_int_2  IN  NUMBER)
  RETURN NUMBER
AS
  l_request   soap_api.t_request;
  l_response  soap_api.t_response;
  l_return    VARCHAR2(32767);
  
  l_url          VARCHAR2(32767);
  l_namespace    VARCHAR2(32767);
  l_method       VARCHAR2(32767);
  l_soap_action  VARCHAR2(32767);
  l_result_name  VARCHAR2(32767);
BEGIN
  l_url         := 'http://www.oracle-base.com/webservices/server.php';
  l_namespace   := 'xmlns="http://www.oracle-base.com/webservices/"';
  l_method      := 'ws_add';
  l_soap_action := 'http://www.oracle-base.com/webservices/server.php/ws_add';
  l_result_name := 'return';


El anterior bloque de código es únicamente para definir variables:

  • l_url es la url real donde se puede acceder al web service.
  • l_namespace es el espacio de nombres del web service. Más información sobre namespaces en este enlace.
  • l_method es el nombre del método del web service que se va a utilizar.
  • l_soap_action es la acción que se requiere en la petición SOAP. Por lo que he visto en el caso de los web services hechos con .NET, suele ser un URI formado por el valor del namespace (el valor de "xmlns") y el nombre del método.
  • l_result_name es el nombre del resultado que nos da el web service. En el caso de web services desarrollados con .NET, el nombre de esta respuesta suele ser el nombre del método seguido por "Result". Ejemplo: "MiMetodoWebResult " si el nombre del metodo es "MiMetodoWeb".



l_request := soap_api.new_request(p_method       => l_method,
                                    p_namespace    => l_namespace);


En esta llamada creamos el requerimiento al web service, noten que le pasamos unicamente el nombre del método y el espacio de nombres.


soap_api.add_parameter(p_request => l_request,
                         p_name    => 'int1',
                         p_type    => 'xsd:integer',
                         p_value   => p_int_1);

  soap_api.add_parameter(p_request => l_request,
                         p_name    => 'int2',
                         p_type    => 'xsd:integer',
                         p_value   => p_int_2);


En este bloque, añadimos los parámetros a nuestro requerimiento. Se debe hacer una llamada por parámetro, indicándole el request, el nombre del parámetro, el tipo del parámetro y el valor del mismo. Para más información acerca de los tipos de parámetros, puede ir a este enlace.


l_response := soap_api.invoke(p_request => l_request,
                                p_url     => l_url,
                                p_action  => l_soap_action);


Con esto ya se invoca el método del web service que vamos a consumir. Es el momento en el que se envía la petición y se obtiene la respuesta por parte del servicio web.


l_return := soap_api.get_return_value(p_response  => l_response,
                                        p_name      => l_result_name,
                                        p_namespace => NULL);


Esta función get_return_value es la encargada de darnos las respuestas que nos da el web service. Nótese que a diferencia de los métodos anteriores, en lugar de la petición, ahora le pasamos como parámetro la "response".

El parámetro p_name se refiere al nombre del resultado; aquí deseo aclarar que, en el caso de .NET, cuando se establecen parámetros en el método web como "ByRef", es que son parámetros pasados por referencia, por lo que cualquier cambio que tengan será reflejado.Así, si por ejemplo, el parámetro int1 estuviera como ByRef en la firma del método ws_add, entonces se puede obtener un valor de esa variable utilizando el método get_return_value, indicándole como p_name el nombre del parámetro tal como en la llamada a la función add_paramenter.

El parámetro p_namespace normalmente no lleva nada o puede llevar también el valor de la variable l_namespace.

Por último, ¿Que tal si al menos uno de los parámetros es un XML? Sencillo, todos los textos con formato XML han de enviarase encerrado por la etiqueta <![CDATA[ ]]>, así:


soap_api.add_parameter(p_request => l_request,
                         p_name    => 'v_xml',
                         p_type    => 'xsd:string',
                         p_value   => '<![CDATA[' || p_xml || ']]>');


Esta es una llamada de muestra, en caso el método web tuviera un parámetro llamado "v_xml" que requiriera un xml pero es tipo string.

Y así, de esta manera, pueden estar realizando una función que consuma un servicio web en unos cuantos minutos.

Espero esto les sea de ayuda y esté lo bastante claro. Saludos.

13 comentarios:

  1. Hola saludos Wilson! Está muy bueno tu post, justamente se me ha solicitado lo mismo en mi trabajo, en mi caso uno de los métodos del web service tiene como parámetro una tabla de x valores, sabes cómo se le enviaría una tabla o arreglo utilizando ésta forma? De antemano agradezco tu atención. Saludos.

    ResponderEliminar
    Respuestas
    1. Buenos días Juan Carlos. Me alegra que te sirva de algo mi post.
      Fíjate que no tengo ni idea, pero te puedo ayudar con una guía: Dos cosas bien importantes a la hora de ver los parámetros de un web service son su tipo y la compatibilidad entre ambos. No entiendo del todo el concepto de tabla que dices inicialmente, así que tomaré el de arreglo de valores. Lo primero que tendrías que ver es que los tipos de datos que almacene el arreglo de valores que se reciben sean compatibles con los tipos de valores que almacene el arreglo que vas a enviar. Segundo, pon atención a la función "add_parameter" del soap_api, el valor del parámetro "p_type" es el tipo de datos estandar que acepta el web service; con estandar, me refiero a que es un tipo de datos que nuestro protocolo conoce (en este caso SOAP), no que conoce el programa cliente ni el servicio web; en este caso, "xsd:string" indica por medio de SOAP que el parámetro es un string; si fuera numérico, sería "xsd:integer" ó "xsd:int". Una lista de estos tipos la puedes encontrar en:
      http://infohost.nmt.edu/tcc/help/pubs/rnc/xsd.html
      Para utilizar arrays, puedes leer el siguiente post para darte una idea de lo que necesitarías:
      http://www.phpeveryday.com/articles/Web-Services-WSDL-Array-Array-Type-P517.html
      Básicamente tendrías que crear un esquema personalizado donde definas el tipo complejo que, en tu caso, sería tu array.
      Se ve que es una tarea interesante. Espero haberte encaminado para encontrar la solución a tu requerimiento.
      Saludos.

      Eliminar
  2. Has conseguido que funcione???
    he copiado el ejemplo add_number y soap_api y me da error

    ORA-29273: HTTP request failed
    ORA-06512: at "SYS.UTL_HTTP",line 1029
    ORA-12541: TNS:no listener
    ORA-06512: at "dbtest.SOAP_API",line 144
    ORA-06512: at "dbtest.ADD_NUMBERS", line 34 FROM!

    ResponderEliminar
  3. mmm... fíjate que lo probé ahorita en la oficina, y me da un error similar, pero eso es porque el servidor donde está la base de datos no tiene conectividad hacia el exterior. Puede que sea lo mismo con tu base de datos, que tengas algún firewall o algo bloqueandola. Primero vería que el puerto 1521, que es el puerto del servidor de oracle, esté libre y pueda conectarse a internet, ya que eso hace, conectarse a un servidor en la nube para consumir el servicio web hospedado en él.
    Saludos.

    ResponderEliminar
  4. Según entiendo la llamada se hace desde la base de datos, hay alguna configuración especial que deba de tener el servidor, apache o el IIS o simplemente desde lo paquetes de la base de datos hace la invocación al puerto 80 de la otra maquina?

    ResponderEliminar
    Respuestas
    1. En efecto, la llamada se hace desde la base de datos; no necesita de ninguna configuración especial, simplemente se publica el web service y debe asegurarse de que el servidor de la base de datos tenga comunicación con el servidor web; la url del web service es un parámetro para el paquete:

      l_url := 'http://www.oracle-base.com/webservices/server.php';

      Si el web service corriera en el puerto 8080, por ejemplo, entonces cambiaría a:

      l_url := 'http://www.oracle-base.com:8080/webservices/server.php';

      Todo se puede configurar por medio de las variables que indico en el primer bloque de código del post (l_url, l_namespace, l_method, l_soap_action y l_result_name), incluso podría hacerlos parámetros de la función en lugar de variables.

      Espero haya sido de ayuda. Saludos.

      Eliminar
  5. Este comentario ha sido eliminado por el autor.

    ResponderEliminar
  6. Hola! Ante todo muy buena info, ya había visto las páginas de donde tomas el ejemplo pero no entendía nada. Te hago una consulta, tengo que crear y correr un concurrente en eBS que cargue el XML que llame a estos web services. Tenés alguna idea de ésto? Cuáles serían los parámetros? Gracias !!

    ResponderEliminar
  7. Saludos, gracias por tu blog es de gran ayuda, yo no tengo experiencia con oracle espero que me puedas ayudar, necesito consumir WebServices pero estos están con certificado digital HTTPS, puesto q' ahi el codigo emite errores.
    gracias por tu tiempo

    ResponderEliminar
  8. hola Giovanny, para este caso debes utilizar la herramienta orcle wallet

    ResponderEliminar
  9. hola Wilson
    necesito enlazarme con un ws pero sale error

    Error: PLS-00201: el identificador 'SOAP_API.T_REQUEST' se debe declarar
    Line: 4
    Text: l_request soap_api.t_request;

    Error: PLS-00201: el identificador 'SOAP_API.GET_RETURN_VALUE' se debe declarar
    Line: 37

    para que reconozca SOAP_API. se debe hacer algo adicional?
    Diana Montalvo

    ResponderEliminar
  10. Necesito configurar asistente oracle net 8 y no me deja conectarme a la base de datos oracle 11g, si alguien ha tenido problema similar desearia una ayuda.

    ResponderEliminar
  11. hola buenas.
    Alguien me puede ayudar. Tengo respuesta del webservice, pero la respuesta me manda xml, como podria leer toda la información y guardarla en una tabla.

    ResponderEliminar