Dnes si ukážeme, jak získat data z Webové služby (WebService) přímo v PL/SQL, jak jsem sliboval v předchozím článku.

[English version – Calling Web Services in PL/SQL using UTL_HTTP package]

K tomuto účelu potřebujete:

  1. Nástroj pro vývoj/testování webových služeb. Použijeme balíček SoapUI
  2. Prostředí SQL*Plus. Pro naše testy bude plně vyhovovat.

Vytvoření testovací webové služby

Vytvoříme testovací webovou službu popsanou následujícím WSDL dokumentem.

<?xml version="1.0" encoding="UTF-8"?>
<definitions name="HelloService"
   targetNamespace="http://www.ecerami.com/wsdl/HelloService.wsdl"
   xmlns="http://schemas.xmlsoap.org/wsdl/"
   xmlns:soap="http://schemas.xmlsoap.org/wsdl/soap/"
   xmlns:tns="http://www.ecerami.com/wsdl/HelloService.wsdl"
   xmlns:xsd="http://www.w3.org/2001/XMLSchema">

   <message name="SayHelloRequest">
      <part name="firstName" type="xsd:string"/>
   </message>
   <message name="SayHelloResponse">
      <part name="greeting" type="xsd:string"/>
   </message>

   <portType name="Hello_PortType">
      <operation name="sayHello">
         <input message="tns:SayHelloRequest"/>
         <output message="tns:SayHelloResponse"/>
      </operation>
   </portType>

   <binding name="Hello_Binding" type="tns:Hello_PortType">
      <soap:binding style="rpc"
         transport="http://schemas.xmlsoap.org/soap/http"/>
      <operation name="sayHello">
         <soap:operation soapAction="sayHello"/>
         <input>
            <soap:body
               encodingStyle="http://schemas.xmlsoap.org/soap/encoding/"
               namespace="urn:examples:helloservice"
               use="encoded"/>
         </input>
         <output>
            <soap:body
               encodingStyle="http://schemas.xmlsoap.org/soap/encoding/"
               namespace="urn:examples:helloservice"
               use="encoded"/>
         </output>
      </operation>
   </binding>

   <service name="Hello_Service">
      <documentation>WSDL File for HelloService</documentation>
      <port binding="tns:Hello_Binding" name="Hello_Port">
         <soap:address
            location="http://localhost:8088/mockHello_Binding"/>
      </port>
   </service>
</definitions>

Nainstalujeme a spustíme program SoapUI. Nový projekt v SoapUI vytvoříme přes položku v menu File > New Soap UI Project.

Vytvoření nového projektu SoapUI
Vytvoření nového projektu SoapUI

Vygenerujeme Mock službu (Mock = falešný – lepší překlad jsem nanašel) – Obrázek ukazuje, že WSDL dokument popisuje službu s jednou metodou „sayHello“.

Generování Mock service
Generování Mock service

Vyplníme název Mock service – ponechávám implicitní hodnotu, kterou nabídne SoapUI.

Název Mock service
Název Mock service

Spustíme testovací službu (pokud jste ponechali implicitní nastavení, služba se namapuje na port 8088).

Spuštění testovací webové služby
Spuštění testovací webové služby

Otevřeme a zobrazíme WSDL dokument v okně prohlížeče.

Otevření WSDL v okně prohlížeče
Otevření WSDL v okně prohlížeče

Takto vypadá okno prohlížeče s výsledným WSDL, dokumentem který vrací testovací Webová služba „HelloService“.

IE zobrazuje WSDL dokument testovací Webové služby
IE zobrazuje WSDL dokument testovací Webové služby

Spustíme dotaz nad metodu sayHello. Měly byste vidět následující.

Spuštění dotazu na sayHello
Spuštění dotazu na sayHello

Obsah dotazu na server (SOAP Request):

<soapenv:Envelope xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:soapenv="http://schemas.xmlsoap.org/soap/envelope/" xmlns:urn="urn:examples:helloservice">
   <soapenv:Header/>
   <soapenv:Body>
      <urn:sayHello soapenv:encodingStyle="http://schemas.xmlsoap.org/soap/encoding/">
         <firstName xsi:type="xsd:string">Martin Mareš</firstName>
      </urn:sayHello>
   </soapenv:Body>
</soapenv:Envelope>

Obsah odpovědi serveru (SOAP Response):

<soapenv:Envelope xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:soapenv="http://schemas.xmlsoap.org/soap/envelope/" xmlns:urn="urn:examples:helloservice">
   <soapenv:Header/>
   <soapenv:Body>
      <urn:sayHelloResponse soapenv:encodingStyle="http://schemas.xmlsoap.org/soap/encoding/">
         <greeting xsi:type="xsd:string">gero et</greeting>
      </urn:sayHelloResponse>
   </soapenv:Body>
</soapenv:Envelope>

Konzumace webové služby pomocí PL/SQL balíku UTL_HTTP

Necháme naši testovací Mock service běžet v okně SoapUI a pustíme se do PL/SQL kódu SQL*Plus. Předpokladem pro správné fungování následujícího PL/SQL kódu je, že se dostanete z vaší vývojové/testovací databáze Oracle na počítač, kde máte spuštěnu testovací Webovou službu na portu 8088.

Při spuštění kódu konzumujícího webovou službu se může objevit následující chyba:

ORA-29273: selhal požadavek HTTP
ORA-06512: na "SYS.UTL_HTTP", line 1130
ORA-24247: seznam řízení přístupu (ACL) nepovolil přístup k síti
ORA-06512: na line 22

V případě, že se tak stane, je potřeba nastavit oprávnění pro volání connect a resolve z Oracle databáze. Zajistíme to následujícím skriptem (v mém případě spouštěný pro uživatele SCOTT). U skriptu prosím věnujte pozornost položce principal (ta by měla obsahovat jméno uživatele, pro nějž oprávnění přiřazujete), dále položce privilege (název privilegia „connect“ a „resolve“) a položce host (můžete vyplnit buď HOSTNAME nebo IP adresu počítače, kde běží webová služba)

BEGIN
  DBMS_NETWORK_ACL_ADMIN.CREATE_ACL(acl         => 'www.xml',
                                    description => 'Test ACL',
                                    principal   => 'SCOTT',
                                    is_grant    => true,
                                    privilege   => 'connect');

  DBMS_NETWORK_ACL_ADMIN.ADD_PRIVILEGE(acl       => 'www.xml',
                                       principal => 'SCOTT',
                                       is_grant  => true,
                                       privilege => 'resolve');

  DBMS_NETWORK_ACL_ADMIN.ASSIGN_ACL(acl  => 'www.xml',
                                    host => 'MY-NOTEBOOK-HOSTNAME');
END;
/
COMMIT
/

Pokud nastavíte správně výše zmíněná opravnění, pak by měl začít fungovat následující skript demonstrující Request/Response naší testovací webové služby. (U skriptu věnujte pozornost proměnným l_host_name a l_port, hodnoty by měly odpovídat těm vámi nastaveným)

DECLARE
    l_http_request   UTL_HTTP.req;
    l_http_response  UTL_HTTP.resp;
    l_buffer_size    NUMBER(10) := 512;
    l_line_size      NUMBER(10) := 50;
    l_lines_count    NUMBER(10) := 20;
    l_string_request VARCHAR2(512);
    l_line           VARCHAR2(128);
    l_substring_msg  VARCHAR2(512);
    l_raw_data       RAW(512);
    l_clob_response  CLOB;
    l_host_name      VARCHAR2(128) := 'MY-NOTEBOOK-HOSTNAME';
    l_port           VARCHAR2(128) := '8088';

BEGIN
    l_string_request := '<soapenv:Envelope xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:soapenv="http://schemas.xmlsoap.org/soap/envelope/" xmlns:urn="urn:examples:helloservice">
   <soapenv:Header/>
   <soapenv:Body>
      <urn:sayHello soapenv:encodingStyle="http://schemas.xmlsoap.org/soap/encoding/">
         <firstName xsi:type="xsd:string">Martin</firstName>
      </urn:sayHello>
   </soapenv:Body>
</soapenv:Envelope>';
    UTL_HTTP.set_transfer_timeout(60);
    l_http_request := UTL_HTTP.begin_request(url => 'http://' || l_host_name || ':' || l_port || '/mockHello_Binding', method => 'POST', http_version => 'HTTP/1.1');
    UTL_HTTP.set_header(l_http_request, 'User-Agent', 'Mozilla/4.0');
    UTL_HTTP.set_header(l_http_request, 'Host', l_host_name || ':' || l_port);
    UTL_HTTP.set_header(l_http_request, 'Connection', 'close');
    UTL_HTTP.set_header(l_http_request, 'Content-Type', 'text/xml;charset=UTF-8');
    UTL_HTTP.set_header(l_http_request, 'SOAPAction', '"sayHello"');
    UTL_HTTP.set_header(l_http_request, 'Content-Length', LENGTH(l_string_request));

    <<request_loop>>
    FOR i IN 0..CEIL(LENGTH(l_string_request) / l_buffer_size) - 1 LOOP
        l_substring_msg := SUBSTR(l_string_request, i * l_buffer_size + 1, l_buffer_size);

        BEGIN
            l_raw_data := utl_raw.cast_to_raw(l_substring_msg);
            UTL_HTTP.write_raw(r => l_http_request, data => l_raw_data);
            EXCEPTION
                WHEN NO_DATA_FOUND THEN
                    EXIT request_loop;
        END;
    END LOOP request_loop;

    l_http_response := UTL_HTTP.get_response(l_http_request);
    DBMS_OUTPUT.put_line('Response> status_code: "' || l_http_response.status_code || '"');
    DBMS_OUTPUT.put_line('Response> reason_phrase: "' ||l_http_response.reason_phrase || '"');
    DBMS_OUTPUT.put_line('Response> http_version: "' ||l_http_response.http_version || '"');

    BEGIN

        <<response_loop>>
        LOOP
            UTL_HTTP.read_raw(l_http_response, l_raw_data, l_buffer_size);
            l_clob_response := l_clob_response || UTL_RAW.cast_to_varchar2(l_raw_data);
        END LOOP response_loop;

        EXCEPTION
            WHEN UTL_HTTP.end_of_body THEN
                UTL_HTTP.end_response(l_http_response);
    END;
    DBMS_OUTPUT.put_line('Response> length: "' || LENGTH(l_clob_response) || '"');
    DBMS_OUTPUT.put_line(CHR(10) || '=== Print first ' || l_lines_count || ' lines of HTTP response... ===' || CHR(10) || CHR(10));

    <<print_response>>
    FOR i IN 0..CEIL(LENGTH(l_clob_response) / l_line_size) - 1 LOOP
        l_line := SUBSTR(l_clob_response, i * l_line_size + 1, l_line_size);
        DBMS_OUTPUT.put_line('[' || LPAD(i, 2, '0') || ']: ' || l_line);
        EXIT WHEN i > l_lines_count - 1;
    END LOOP print_response;

    IF l_http_request.private_hndl IS NOT NULL THEN
        UTL_HTTP.end_request(l_http_request);
    END IF;

    IF l_http_response.private_hndl IS NOT NULL THEN
        UTL_HTTP.end_response(l_http_response);
    END IF;

END;
/

Výstup skriptu je následující:

Response> status_code: "200"
Response> reason_phrase: "OK"
Response> http_version: "HTTP/1.1"
Response> length: "483"

=== Print first 20 lines of HTTP response... ===

[00]: <soapenv:Envelope xmlns:xsi="http://www.w3.org/200
[01]: 1/XMLSchema-instance" xmlns:xsd="http://www.w3.org
[02]: /2001/XMLSchema" xmlns:soapenv="http://schemas.xml
[03]: soap.org/soap/envelope/" xmlns:urn="urn:examples:h
[04]: elloservice">
   <soapenv:Header/>
   <soapenv:B
[05]: ody>
      <urn:sayHelloResponse soapenv:encoding
[06]: Style="http://schemas.xmlsoap.org/soap/encoding/">
[07]:
         <greeting xsi:type="xsd:string">gero et
[08]: </greeting>
      </urn:sayHelloResponse>
   </s
[09]: oapenv:Body>
</soapenv:Envelope>

Konzumace reálné webové služby Weather CDYNE

Jako „bombónek“ na závěr si ukážeme konzumaci reálné webové služby Weather CDYNE (WSDL popis), konkrétně volání metody GetCityForecastByZIP. Volání následujícího skriptu předpokládá nastavení práva (ACL) v databázi Oracle (podobně, jak jsem zmínil výše v článku):

BEGIN
  DBMS_NETWORK_ACL_ADMIN.ASSIGN_ACL(acl  => 'www.xml',
                                    host => 'ws.cdyne.com');
END;
/
COMMIT
/

A teď už slíbený skript:

DECLARE
    l_http_request    UTL_HTTP.req;
    l_http_response   UTL_HTTP.resp;
    l_buffer_size     NUMBER(10) := 512;
    l_line_size       NUMBER(10) := 50;
    l_lines_count     NUMBER(10) := 20;
    l_string_request  VARCHAR2(512);
    l_line            VARCHAR2(128);
    l_substring_msg   VARCHAR2(512);
    l_raw_data        RAW(512);
    l_clob_response   CLOB;
    l_host_name       VARCHAR2(128) := 'ws.cdyne.com';
    l_port            VARCHAR2(128) := '80';
    l_zip             VARCHAR2(128) := '94065'; -- ZIP for Oracle corporation (Redwood City)
    l_resp_xml        XMLType;
    l_result_XML_node VARCHAR2(128);
    l_NAMESPACE_SOAP  VARCHAR2(128) := 'xmlns="http://www.w3.org/2003/05/soap-envelope"';
    l_response_city   VARCHAR2(128);
    l_response_date   VARCHAR2(128);
    l_response_temp   VARCHAR2(128);

BEGIN
    l_string_request := '<?xml version="1.0" encoding="utf-8"?>
<soap12:Envelope xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:soap12="http://www.w3.org/2003/05/soap-envelope">
  <soap12:Body>
    <GetCityForecastByZIP xmlns="http://ws.cdyne.com/WeatherWS/">
      <ZIP>' || l_zip || '</ZIP>
    </GetCityForecastByZIP>
  </soap12:Body>
</soap12:Envelope>';
    UTL_HTTP.set_transfer_timeout(60);
    l_http_request := UTL_HTTP.begin_request(url => 'http://' || l_host_name || ':' || l_port || '/WeatherWS/Weather.asmx', method => 'POST', http_version => 'HTTP/1.1');
    UTL_HTTP.set_header(l_http_request, 'User-Agent', 'Mozilla/4.0');
    UTL_HTTP.set_header(l_http_request, 'Connection', 'close');
    UTL_HTTP.set_header(l_http_request, 'Content-Type', 'application/soap+xml; charset=utf-8');
    UTL_HTTP.set_header(l_http_request, 'Content-Length', LENGTH(l_string_request));

    <<request_loop>>
    FOR i IN 0..CEIL(LENGTH(l_string_request) / l_buffer_size) - 1 LOOP
        l_substring_msg := SUBSTR(l_string_request, i * l_buffer_size + 1, l_buffer_size);

        BEGIN
            l_raw_data := utl_raw.cast_to_raw(l_substring_msg);
            UTL_HTTP.write_raw(r => l_http_request, data => l_raw_data);
            EXCEPTION
                WHEN NO_DATA_FOUND THEN
                    EXIT request_loop;
        END;
    END LOOP request_loop;

    l_http_response := UTL_HTTP.get_response(l_http_request);
    DBMS_OUTPUT.put_line('Response> status_code: "' || l_http_response.status_code || '"');
    DBMS_OUTPUT.put_line('Response> reason_phrase: "' ||l_http_response.reason_phrase || '"');
    DBMS_OUTPUT.put_line('Response> http_version: "' ||l_http_response.http_version || '"');

    BEGIN

        <<response_loop>>
        LOOP
            UTL_HTTP.read_raw(l_http_response, l_raw_data, l_buffer_size);
            l_clob_response := l_clob_response || UTL_RAW.cast_to_varchar2(l_raw_data);
        END LOOP response_loop;

        EXCEPTION
            WHEN UTL_HTTP.end_of_body THEN
                UTL_HTTP.end_response(l_http_response);
    END;
    DBMS_OUTPUT.put_line('Response> length: "' || LENGTH(l_clob_response) || '"');
    DBMS_OUTPUT.put_line(CHR(10) || '=== Print result ===' || CHR(10) || CHR(10));

    IF(l_http_response.status_code = 200) THEN
        -- Create XML type from response text
        l_resp_xml := XMLType.createXML(l_clob_response);
        -- Clean SOAP header
        SELECT EXTRACT(l_resp_xml, 'Envelope/Body/node()', l_NAMESPACE_SOAP) INTO l_resp_xml FROM dual;
        -- Extract City
        l_result_XML_node := 'GetCityForecastByZIPResponse/GetCityForecastByZIPResult/';
        SELECT EXTRACTVALUE(l_resp_xml, l_result_XML_node || 'City[1]', 'xmlns="http://ws.cdyne.com/WeatherWS/"') INTO l_response_city FROM dual;
        SELECT EXTRACTVALUE(l_resp_xml, l_result_XML_node || 'ForecastResult[1]/Forecast[1]/Date[1]', 'xmlns="http://ws.cdyne.com/WeatherWS/"') INTO l_response_date FROM dual;
        SELECT EXTRACTVALUE(l_resp_xml, l_result_XML_node || 'ForecastResult[1]/Forecast[1]/Temperatures[1]/DaytimeHigh[1]', 'xmlns="http://ws.cdyne.com/WeatherWS/"') INTO l_response_temp FROM dual;
    END IF;

    DBMS_OUTPUT.put_line ( 'Result> l_response_city=' || l_response_city);
    DBMS_OUTPUT.put_line ( 'Result> l_response_date=' || l_response_date);
    DBMS_OUTPUT.put_line ( 'Result> l_response_temp=' || l_response_temp);

    IF l_http_request.private_hndl IS NOT NULL THEN
        UTL_HTTP.end_request(l_http_request);
    END IF;

    IF l_http_response.private_hndl IS NOT NULL THEN
        UTL_HTTP.end_response(l_http_response);
    END IF;

END;
/

Výstup skriptu je následující:

Response> status_code: "200"
Response> reason_phrase: "OK"
Response> http_version: "HTTP/1.1"
Response> length: "2673"

=== Result... ===

Result> l_response_city=Redwood City
Result> l_response_date=2010-08-17T00:00:00
Result> l_response_temp=73

Post to Twitter

2 Comments

  1. James Taylor

    Odpovědět

    Thanks Martin! That tool saved my bacon! SOAPUI Pro quickly created a test harness and an envelope that showed me where my envelope was improperly formed. A big trick is that when an ASMX web service has a string input parameter that contains complex XML it needs to be wrapped with the following: ![CDATA[ ….]]

    Here is a snippet that should help anyone do HTTPS WebServices with UTL_HTTP. Remember that you will need rights on the package, an ACL created to access the endpoint url and port with rights granted to your user, and a Wallet created that contains the entire security tree heirarchy of certs for it to work. And remember the trick above with xml input strings and if the request xml is larger than 32K you will need to follow a „chunking“ methodology.

    set define off;
    /
    create or replace
    FUNCTION apps.get_insurance_info RETURN XMLTYPE
    AS
    http_req UTL_HTTP.req;
    http_resp UTL_HTTP.resp;
    reqlength BINARY_INTEGER;
    responsebody CLOB := NULL;
    resplength BINARY_INTEGER;
    buffer VARCHAR2 (32767);
    p_env CLOB := NULL;
    amount PLS_INTEGER := 500;
    offset PLS_INTEGER := 1;
    reslength BINARY_INTEGER;
    eob BOOLEAN := FALSE;
    l_requestXML varchar(32767);
    l_responseXML XMLTYPE;
    BEGIN

    p_env := ‚

    E.2<![CDATA[……..]]>

    ‚;

    UTL_HTTP.SET_WALLET(‚file:/ebstest/product/11202/admin/wallet‘, ‚MyWalletPassword123‘);

    utl_http.set_response_error_check(enable => TRUE);
    utl_http.set_detailed_excp_support(enable => TRUE);
    –UTL_HTTP.SET_TRANSFER_TIMEOUT(timeout => 3);

    HTTP_REQ := UTL_HTTP.BEGIN_REQUEST (‚https://www.pverify.net/EDICare/Service.asmx‘, ‚POST‘, ‚HTTP/1.1‘);

    UTL_HTTP.set_body_charset(HTTP_REQ, ‚UTF-8‘);
    UTL_HTTP.SET_HEADER (HTTP_REQ, ‚Content-Type‘, ‚text/xml‘);
    –utl_http.set_header(HTTP_REQ, ‚User-Agent‘, ‚Mozilla/4.0‘);
    UTL_HTTP.SET_HEADER (HTTP_REQ, ‚SOAPAction‘, ‚http://tempuri.org/RunInquiry‘);
    UTL_HTTP.SET_HEADER (HTTP_REQ, ‚Content-Length‘, length(p_env));
    –UTL_HTTP.SET_HEADER (HTTP_REQ, ‚Transfer-Encoding‘, ‚chunked‘);

    reqlength := DBMS_LOB.getlength (p_env);

    WHILE (offset 0
    THEN
    DBMS_LOB.writeappend (responsebody, LENGTH (buffer), buffer);
    END IF;
    EXCEPTION
    WHEN UTL_HTTP.END_OF_BODY
    THEN
    eob := TRUE;
    END;
    END LOOP;

    UTL_HTTP.end_response (http_resp);

    responsebody := replace(responsebody ,'<‚,“);

    RETURN XMLTYPE.createXML(responsebody);

    DBMS_LOB.freetemporary (responsebody);
    END;
    /

    select apps.get_insurance_info() from dual;

Leave a comment

Vaše emailová adresa nebude zveřejněna. Vyžadované informace jsou označeny *