Llamar a servicio web desde pl/sql

He leído hace poco un artículo antiguo (de 2006) para invocar un servicio web desde la base de datos con pl/sql. En principio lo que describe ha de funcionar desde una versión Oracle 9i. El artículo podéis leerlo en Blogging About Oracle y hace una exposición sencilla de los términos y de los inconvenientes a tener en cuenta a la hora de decidirse a hacer esto desde base de datos. Hace referencia a su vez a un artículo publicado por oracle que ya no existe. Ese artículo es necesario porque incluye el código del paquete demo_soap en el que se basan los ejemplos. He buscado el código fuente y lo he encontrado en un post de los foros de Oracle: Need Help in UTL_HTTP. Por si desaparece o lo que sea, me he apuntado el código fuente del paquete:

CREATE OR REPLACE PACKAGE demo_soap AS
  
  /* A type to represent a SOAP RPC request */
  TYPE request IS RECORD (
    method VARCHAR2 (256),
    namespace VARCHAR2 (256),
    BODY VARCHAR2 (32767)
  );

  /* A type to represent a SOAP RPC response */
  TYPE response IS RECORD (
    doc XMLTYPE
  );

  /* * Create a new SOAP RPC request. */
  FUNCTION new_request (method IN VARCHAR2, namespace IN VARCHAR2)
  RETURN request; 

  /* * Add a simple parameter to the SOAP RPC request. */
  PROCEDURE ADD_PARAMETER (
    req   IN OUT NOCOPY request,
    NAME  IN VARCHAR2,
    TYPE  IN VARCHAR2,
    VALUE IN VARCHAR2
  ); /* * Make the SOAP RPC call. */

  FUNCTION invoke (
    req    IN OUT NOCOPY request,
    url    IN VARCHAR2,
    action IN VARCHAR2
  )
  
  RETURN response; /*
  * Retrieve the sipmle return value of the SOAP RPC call. */

  FUNCTION get_return_value (
    resp      IN OUT NOCOPY response,
    NAME      IN VARCHAR2,
    namespace IN VARCHAR2
  )
  RETURN VARCHAR2;
END;
/
SHOW ERRORS
CREATE OR REPLACE PACKAGE BODY demo_soap AS

  FUNCTION new_request(
             method IN VARCHAR2, 
             namespace IN VARCHAR2)
  RETURN request AS 
    req request; 
  BEGIN 
    req.method := method; 
    req.namespace := namespace;

    RETURN req;
  END;

  PROCEDURE add_parameter(
              req IN OUT NOCOPY request,
              name IN VARCHAR2, 
              type IN VARCHAR2, 
              value IN VARCHAR2)
  AS 
  BEGIN 
    req.body := req.body ||
    ''||value||'';
  END;

  PROCEDURE generate_envelope(
              req IN OUT NOCOPY request, 
              env IN OUT NOCOPY VARCHAR2)
  AS 
  BEGIN 
    env := '

    '||
    req.body||''; 
  END;

  PROCEDURE show_envelope(env IN VARCHAR2)  AS 
    i pls_integer; 
    len pls_integer; 
  BEGIN 
    i := 1; len:= length(env); 
    WHILE (i <= len) LOOP 
      dbms_output.put_line(substr(env, i, 60));
      i := i + 60;
    END LOOP;
  END;

  PROCEDURE check_fault(resp IN OUT NOCOPY response) AS 
    fault_node xmltype; 
    fault_code VARCHAR2(256); 
    fault_string VARCHAR2(32767); 
  BEGIN 
    fault_node := resp.doc.extract('/soap:Fault', 'xmlns:soap="http://schemas.xmlsoap.org/soap/envelope/'); 
    IF (fault_node IS NOT NULL) THEN 
      fault_code := fault_node.extract('/soap:Fault/faultcode/child::text()', 'xmlns:soap="http://schemas.xmlsoap.org/soap/envelope/').getstringval();
      fault_string := fault_node.extract('/soap:Fault/faultstring/child::text()', 'xmlns:soap="http://schemas.xmlsoap.org/soap/envelope/').getstringval();
      raise_application_error(-20000, fault_code || ' - ' || fault_string);
    END IF;
  END;

  FUNCTION invoke(req IN OUT NOCOPY request, 
    url IN VARCHAR2, 
    action IN VARCHAR2) 
  RETURN response AS 
    env VARCHAR2(32767); 
    http_req utl_http.req; 
    http_resp utl_http.resp;
    resp response; 
  BEGIN 
    generate_envelope(req, env); -- show_envelope(env); 
    http_req := utl_http.begin_request(url, 'POST','HTTP/1.0');
    UTL_HTTP.SET_AUTHENTICATION (HTTP_REQ, 'G016D01/S0162114','Xenios02', 'Basic',true); 
    utl_http.set_header(http_req, 'Content-Type', 'text/xml');
    utl_http.set_header(http_req,'Content-Length', length(env)); 
    utl_http.set_header(http_req, 'SOAPAction', action); 
    utl_http.write_text(http_req,env); 
    http_resp := utl_http.get_response(http_req); 
    utl_http.read_text(http_resp, env); 
    utl_http.end_response(http_resp);
    resp.doc := xmltype.createxml(env); 
    resp.doc := resp.doc.extract('/soap:Envelope/soap:Body/child::node()',
    'xmlns:soap="http://schemas.xmlsoap.org/soap/envelope/"'); -- show_envelope(resp.doc.getstringval());
    check_fault(resp); 
    RETURN resp; 
  END; 

  FUNCTION get_return_value(
            resp IN OUT NOCOPY response,
            name IN VARCHAR2,
            namespace IN VARCHAR2)
  RETURN VARCHAR2 AS 
  BEGIN 
    RETURN resp.doc.extract('//'||name||'/child::text()',namespace).getstringval();
  END;

END;
/
SHOW ERRORS

Y también me he apuntado el código de ejemplo de una llamada a un servicio web que aún está vivo y que pide la hora de un Zip code:

CREATE OR REPLACE PACKAGE time_service AS
  FUNCTION get_local_time(zipcode IN VARCHAR2) RETURN VARCHAR2;
END;
/
SHOW ERRORS
CREATE OR REPLACE PACKAGE BODY time_service AS 
  -- Location of Web service definition
  -- http://www.ripedev.com/webservices/LocalTime.asmx?WSDL
  FUNCTION get_local_time(zipcode IN VARCHAR2) RETURN VARCHAR2 IS
    req demo_soap.request;
    resp demo_soap.response;
  BEGIN
    req := demo_soap.new_request('LocalTimeByZipCode',
    'xmlns="http://ripedev.com/xsd/ZipCodeResults.xsd"');
    demo_soap.add_parameter(req, 'ZipCode', 'xsd:string', zipcode);
    resp := demo_soap.invoke(req,
    'http://www.ripedev.com/webservices/LocalTime.asmx',
    'http://ripedev.com/xsd/ZipCodeResults.xsd/LocalTimeByZipCode');
    RETURN demo_soap.get_return_value(resp, 'LocalTimeByZipCodeResult',
    'xmlns="http://ripedev.com/xsd/ZipCodeResults.xsd"');
  END;

BEGIN
  /*
  * Since the Web service resides outside of the firewall, we need to set
  * the proxy in the current session before invoking the service.
  */
  utl_http.set_proxy('proxy.xxx.com', NULL);
  utl_http.set_persistent_conn_support(TRUE);
END;
/
SHOW ERRORS

He hecho una prueba sencilla me ha funcionado:

SET SERVEROUTPUT ON
SET TIMING ON
BEGIN
  -- He buscado un zip code en http://zip4.usps.com
  DBMS_OUTPUT.PUT_LINE('Hora local de KANSAS CITY' || time_service.get_local_time('64101') );
END;
/

Hora local 11/27/2007 6:48:20 AM

Procedimiento PL/SQL terminado correctamente.

Transcurrido: 00:00:00.44

Tampoco he investigado mucho más y no sé si actualmente es la manera más idónea. Desde luego se me antoja algo cutrecillo para los tiempos en los que vivimos. Igual en la 10g hay ya algo super chulo que puede invocar servicios web desde pl/sql.

Anuncios

9 comentarios en “Llamar a servicio web desde pl/sql

  1. Buenas, estuve probando el codigo que mostras para acceder a los WS y luego de ejecutarlo (compilo bien) me esta dando el siguiente problema:

    “No hay más datos para leer del socket”

    Sabras que puede estar pasando?

    Saludos,
    Ignacio.

  2. Hola Ignacio. La primera vez que lo probé me pasó algo parecido. En mi caso fue porque estaba probando con un ZIP que no existía. Me fui a http://zip4.usps.com/zip4/citytown.jsp y busqué lo que más me sonaba, la ciudad de Kansas City en el estado de Kansas (KS). Prueba con uno de esos zip codes a ver qué tal.

    Igual también puede ser tema del proxy.

  3. hola, estoy trabajando con llamados a WS, con pl/sql, oracle 9i, mi xml lo armo con un select a una tabla, mi variable para el xml es de tipo CLOB, cuando consumo el ws me envia el sgte. error :

    ORA-29266: end-of-body reached

    no he podido encontrar una respuesta adecuada, como dato consumo un ws que transforma los grados y no tengo problemas..

    gracias

  4. mi variable wsrequest es el xml y en la intruccion utl_http.read_text, me envia el error
    ORA-29266: end-of-body reached

    begin
    utl_http.read_text(http_resp, wsRequest);
    exception when others then
    dbms_output.put_line(sqlerrm);
    end;

    me gustaria saber por que el error, ya que si con la misma instruccion consumo un WS que transforma los grados funciona bien.

  5. Saludos, el codigo me funciona muy bien para cuando requiero un solo array de elementos, pero cuando requiero varios, como en el ejemplo de XML que adjunto, entonces se cae, quizas alguien sabe como puedo recorrer el XML para extraer solo ciertos datos?

    JOHN SMITH
    AHO
    3738182000
    23/04/2008
    214.34

    JACK THOMAS
    CTE
    3944182004
    11/04/2008
    395.58

    Gracias de antemano!

  6. No se porque no se mostro correctamente el XML, reemplace los por el signo de resta (-).

    -ArrayofDeposit-
    -Deposit-
    -Beneficiary-JOHN SMITH-/Beneficiary-
    -AccountType-AHO-/AccountType-
    -AccountNumber-3738182000-/AccountNumber-
    -Date-23/04/2008-/Date-
    -Amount-214.34-/Amount-
    -/Deposit-
    -Deposit-
    -Beneficiary-JACK THOMAS-/Beneficiary-
    -AccountType-CTE-/AccountType-
    -AccountNumber-3944182004-/AccountNumber-
    -Date-11/04/2008-/Date-
    -Amount-395.58-/Amount-
    -/Deposit-
    -/ArrayofDeposit-

  7. Hola Marco.
    ¿Qué quieres decir con “se cae”? ¿Te da una excepción o algo?.
    ¿Qué estás utilizando para parsear el xml?

    Un saludo

Los comentarios están cerrados.