Aufruf von Webservices über UTL_HTTP

02.
Juli
2018
Veröffentlicht von: Hildegard Asenbauer

Webservices sind aus der modernen Welt des Datenaustausches nicht mehr wegzudenken. Und so haben Aufrufe auch vermehrt Einzug gehalten in PL/SQL-Routinen. Dieser Artikel soll eine kleine Übersicht über Aufrufvarianten via UTL_HTTP geben.

Aufbau eines Aufrufs

Um einen Webservice aufzurufen, werden folgende Schritte durchlaufen:

  1. Herstellen der Verbindung
  2. Setzen von Header-Variablen
    1. Angabe Content-Type 
    2. Angabe der Länge des Request-Textes
  3. Übermittlung des Requests
  4. Abholen der Response
  5. Beendigung des Calls

Das findet sich in PL/SQL in entsprechenden UTL_HTTP-Aufrufen wieder.  Ein Standard-Aufruf würde dann in etwa so aussehen:

DECLARE
   v_url             VARCHAR2 (300);
   v_length          NUMBER;
   v_text            VARCHAR2 (2000);
   v_request         UTL_HTTP.REQ;
   v_response        UTL_HTTP.RESP;
   v_response_text   VARCHAR2 (3000);
BEGIN
     -- Variablen belegen
     /*
     v_url := <aufzurufende URL>;
     v_text := <Text des Reqest>:
     v_length := <Länge des Request-Textes in Bytes>;
     */

   v_request      :=
      UTL_HTTP.begin_request (url            => v_url,
                              method         => 'POST',  -- oder GET
                              http_version   => UTL_HTTP.HTTP_VERSION_1_1);

   UTL_HTTP.set_header (v_request,
                        'Content-Type',
                        'text/xml; charset=utf-8');
   UTL_HTTP.set_header (v_request,
                        'Content-Length',
                        v_length); 

   UTL_HTTP.write_text (v_request,
                        v_text);

   v_response  := UTL_HTTP.get_response ( v_request);


   UTL_HTTP.read_text (r      => v_response,
                       data   => v_response_text);

   UTL_HTTP.end_response ( v_response);

   -- <Verarbeitung von v_response_text > 
END;
/

Beim Aufbau der Verbindung sind als method GET oder POST erlaubt; was zu wählen ist, hängt vom Webservice ab.Die Längenangaben der Variablen sind dabei natürlich ggf. den erwarteten Längen anzupassen.

Chunks

Sowohl Request als auch Response werden als VARCHAR2 übermittelt. Wenn der zu übertragende Text nicht über die VARCHAR2-Grenze hinausgeht – was i. d. R. der Fall ist - kann er in einem Schritt übertragen werden, wie oben gezeigt.

Es kann aber vorkommen, dass Request und / oder Response zu lang werden bzw. dass zumindest die Möglichkeit dazu besteht. In diesem Fall muss auf CLOB umgestellt werden, und die Übertragung geschieht per Chunks.

Im Falle des Requests bedeutet das, dass die die Längenangabe im Header entfällt. Stattdessen wird der Gegenseite über den Header mitgeteilt, dass der Request gestückelt gesendet wird:

      UTL_HTTP.set_header (v_request, 'Transfer-Encoding', 'chunked');

      v_length         := DBMS_LOB.getlength(v_text); 
      v_chunk_length   := 2000;
      v_offest         := 1;

      WHILE v_offest < v_length LOOP
         DBMS_LOB.read (v_text ,
                        v_chunk_length,
                        v_offest,
                        v_chunk_in);

         UTL_HTTP.write_text (v_request, v_chunk_in);
         v_offest   := v_offest + v_chunk_length;
      END LOOP;     


Bei der Response könnte die Schleife so aussehen:

      DBMS_LOB.createtemporary (v_response_clob, FALSE);
      BEGIN
         LOOP
            UTL_HTTP.read_text (v_response, v_chunk_out, 4000);
            DBMS_LOB.writeappend (v_response_clob, LENGTH (v_chunk_out), v_chunk_out);
         END LOOP;
      EXCEPTION
         WHEN UTL_HTTP.end_of_body
         THEN
            UTL_HTTP.end_response (v_response);
      END;


Proxy

Wird die Verbindung über einen Proxy hergestellt, so muss dieser ebenfalls bekanntgemacht werden, und zwar vor Aufbau der Verbindung über begin_request:

      UTL_HTTP.set_proxy (proxy => <proxy>);  


Aufrufe über https

Wird der Webservice über https aufgerufen, ist ein Wallet zwingend erforderlich: Die vollständige Zertifikatshierarchie der Zielseite (mit Ausnahme des Zertifikats der Seite selber – zumindest ab Version 12c) muss im Wallet abgelegt werden. Darauf wird an dieser Stelle nicht näher eingegangen; es ist aber vielfach beschrieben. 

Der Speicherort des Wallet muss ebenfalls vor Aufbau der Verbindung mitgeteilt werden:

   UTL_HTTP.set_wallet (path => 'file:<vollständiger Pfad zum Wallet>');

Es empfiehlt sich, das Wallet mit auto-login anlegen zu lassen; dann muss das Passwort nicht mitgegeben werden, wie hier gezeigt.


Authentifizierung

In diversen Projekten mussten für Aufrufe unterschiedliche Arten von Authentifizierung implementiert werden, die hier kurz vorgestellt werden sollen.


Authentifizierung über den Request

Die erste Variante hat keinen Einfluss auf den Aufruf des Webservice: Username / Password (oder auch eine vorher abgeholte Session-Id o. ä.) werden als Bestandteil des Requests mitgeschickt und müssen entsprechend eingearbeitet werden, z. B. in das XML eines SOAP-Calls (nein, SOAP ist noch lange nicht ausgestorben, auch wenn JSON auf dem Vormarsch ist).


Authentifizierung über Header-Variable

In diesem Fall wird ein Authorisierungsstring  oder -key über einen Aufruf von UTL_HTTP.set_header übermittelt:

      UTL_HTTP.set_header (v_request,
                           'Authorization',
                           v_key);

Dies geschieht an gleicher Stelle wie bei den anderen Aufrufen von SET_HEADER auch: Nach begin_request.


Authentifizierung über Zertifikat

Diese speziell innerhalb von Organisationen beliebte Methode ist aus PL/SQL-Sicht die aufwendigste. Hier kommt erneut ein Wallet ins Spiel: Ein Administrator muss auf den Datenbank-Server ein passendes Client-Zertifikat in einem Wallet hinterlegen und für den User freigeben (s. u.).

Dieses Zertifikat muss dann in Form eines „Request Context“ eingebunden werden (in folgendem Fall ist beim Wallet auto-login konfiguriert, deshalb kann das Passwort entfallen):

DECLARE
   …
   … 
   v_req_context     UTL_HTTP.REQUEST_CONTEXT_KEY;
BEGIN
      v_req_context      :=
         UTL_HTTP.create_request_context (wallet_path       => <wallet_mit_Zertifikat>,
                                          wallet_password   => NULL);

      v_request       :=
         UTL_HTTP.begin_request (url               => v_url,
                                 method            => 'POST',
                                 http_version      => UTL_HTTP.HTTP_VERSION_1_1,
                                 request_context   => v_req_context);
     
      … 
     
      UTL_HTTP.DESTROY_REQUEST_CONTEXT (request_context => v_req_context);            

      UTL_HTTP.end_response ( v_response);
END;
/


Das ganze Bild

Eine Prozedur zum Aufruf eines Webservice, bei der alle oben genannten Erfordernisse zutreffen, könnte dann z. B. so aussehen (wobei bei Authentifizierung über ein Zertifikat innerhalb einer Organisation i. d. R kein Proxy nötig ist):

PROCEDURE send (p_xml        IN            CLOB,
                p_response      OUT NOCOPY CLOB)
IS
   v_url             VARCHAR2 (200);

   v_req_context     UTL_HTTP.REQUEST_CONTEXT_KEY;
   v_request         UTL_HTTP.REQ;

   v_response        UTL_HTTP.RESP;
   v_response_text   VARCHAR2 (32000);

   v_chunk_in        VARCHAR2 (2000 CHAR);
   v_chunk_out       VARCHAR2 (4000 CHAR);

   v_chunk_length    NUMBER;
   v_clob_length     NUMBER;
   v_offest          NUMBER;

   v_ret             NUMBER;
BEGIN
   v_url           := '<URL>';

   UTL_HTTP.set_wallet (path => '<Pfad zum Wallet für https-Aufruf>');
   UTL_HTTP.set_proxy (proxy => <proxy>);  

   v_req_context      :=
      UTL_HTTP.create_request_context (wallet_path     => '<Pfad zum Wallet mit Zertifikat>',
                                       wallet_password => NULL);

   v_request       :=
      UTL_HTTP.begin_request (url               => v_url,
                              method            => 'POST',
                              http_version      => UTL_HTTP.HTTP_VERSION_1_1,
                              request_context   => v_req_context);


   UTL_HTTP.set_header (v_request,
                        'Content-Type',
                        'text/xml; charset=utf-8');
   UTL_HTTP.set_header (v_request,
                        'Transfer-Encoding',
                        'chunked');
   UTL_HTTP.set_body_charset ('UTF-8');

   v_clob_length   := DBMS_LOB.getlength ( p_xml);
   v_chunk_length  := 2000;
   v_offest        := 1;

   WHILE v_offest < v_clob_length LOOP
      DBMS_LOB.read (p_xml,
                     v_chunk_length,
                     v_offest,
                     v_chunk_in);

      UTL_HTTP.write_text (v_request,
                           v_chunk_in);
      v_offest  := v_offest + v_chunk_length;
   END LOOP;

   v_response      := UTL_HTTP.GET_RESPONSE ( v_request);

   DBMS_LOB.createtemporary (p_response,
                             FALSE);

   BEGIN
      LOOP
         UTL_HTTP.read_text (v_response,
                             v_chunk_out,
                             4000);
         DBMS_LOB.writeappend (p_response,
                               LENGTH ( v_chunk_out),
                               v_chunk_out);
      END LOOP;
   EXCEPTION
      WHEN UTL_HTTP.end_of_body
      THEN
         UTL_HTTP.end_response ( v_response);
   END;

   UTL_HTTP.destroy_request_context ( request_context => v_req_context);
   UTL_HTTP.end_response ( v_response);
END send;

In Abhängigkeit vom jeweiligen Webservice können natürlich noch weitere Header-Angaben und / oder sonstige Aufrufe nötig oder zumindest sinnvoll sein. An dieser Stelle sei auf die Oracle-Dokumentation zum Package UTL_HTTP verwiesen.


Benötigte Access Control List Einträge

Seit Oracle Version 11 müssen Zugriffe auf externe Ressourcen freigegeben werden über Öffnet externen Link in neuem FensterAccess Control Lists ab Version 12c spricht man auch von Access Control Entries.

Im obigen Beispiel können bis zu drei Freigaben nötig sein:

  1. Aufgerufene URL
    Dieser Host (bzw. die Einstiegsadresse) muss in jedem Fall freigegeben werden.
    Privileg: CONNECT
  2. Proxy
    Sofern ein Proxy verwendet wird, muss auch dieser freigegeben werden.
    Privileg CONNECT
  3. Authentifizierung über Client-Zertifikat:
    Hier muss der Zugriff auf das Zertifikat im Wallet freigegeben werden.
    Privileg: USE_CLIENT_CERTIFICATES

Kontrolle der benötigten Freigaben über das Data Dictionary (als Schema-User des Aufrufs):

select *  from user_host_aces   -- Hosts, also Punkt 1 und 2

select * from user_wallet_aces  -- Wallet-Freigabe

Jede Menge Know-how für Sie!

In unserer Know-How Datenbank finden Sie mehr als 300 ausführliche Beiträge zu den Oracle-Themen wie DBA, SQL, PL/SQL, APEX und vielem mehr.
Hier erhalten Sie Antworten auf Ihre Fragen.