【Oracle】UTL_HTTP完全ガイド|PL/SQL から REST API を呼び出す方法・HTTPS・認証ヘッダー・レスポンス取得まで解説

Oracle データベースから直接 REST API や Web サービスを呼び出したい場面があります。バッチ処理中に外部 API へデータを送信する、Webhook を起動する、外部サービスからデータを取得してテーブルに格納するといった要件です。

UTL_HTTP パッケージは PL/SQL から HTTP/HTTPS リクエストを送信できる Oracle 組み込みパッケージです。GET・POST・PUT・DELETE など各種メソッドに対応し、カスタムヘッダーや認証も設定できます。

この記事でわかること

  • UTL_HTTP の基本フロー(BEGIN_REQUEST → SET_HEADER → WRITE_TEXT → GET_RESPONSE → READ_TEXT → END_RESPONSE)
  • GET リクエストと POST リクエスト(JSON ボディ)の書き方
  • Authorization ヘッダーで Bearer トークン認証を設定する方法
  • HTTPS 接続に必要な Oracle ウォレット(WALLET)の設定
  • タイムアウト設定と例外処理・END_RESPONSE の正しい後処理
  • UTL_HTTP に必要な権限と ACL(アクセス制御リスト)の設定
スポンサーリンク

必要な権限と ACL(アクセス制御リスト)の設定

Oracle 11g 以降、外部ネットワークへのアクセスには ACL(アクセス制御リスト)の設定が必要です。SYS または DBA が DBMS_NETWORK_ACL_ADMIN パッケージで許可ホストを登録します。

ACL でアクセス許可ホストを登録する(Oracle 12c 以降の方法)
-- Oracle 12c 以降の簡略化された ACL 設定
-- DBMS_NETWORK_ACL_ADMIN.APPEND_HOST_ACE で特定ホストへのアクセスを許可する
BEGIN
    -- api.example.com への HTTP アクセスを hr_user に許可する
    DBMS_NETWORK_ACL_ADMIN.APPEND_HOST_ACE(
        host => 'api.example.com',   -- 許可するホスト名(ワイルドカード *.example.com も可)
        lower_port => 80,             -- 許可するポート範囲(開始)
        upper_port => 443,            -- 許可するポート範囲(終了)
        ace => xs$ace_type(
            privilege_list => xs$name_list('connect', 'resolve'),
            principal_name => 'HR_USER',
            principal_type => xs_acl.ptype_db
        )
    );
END;
/

-- ACL の設定を確認する
SELECT host, lower_port, upper_port, aclid
FROM DBA_HOST_ACES
WHERE principal = 'HR_USER'
ORDER BY host;

-- UTL_HTTP パッケージへの EXECUTE 権限(通常は PUBLIC に付与済み)
GRANT EXECUTE ON UTL_HTTP TO hr_user;

GET リクエストの基本パターン

UTL_HTTP で GET リクエストを送信する
DECLARE
    v_req      UTL_HTTP.REQ;
    v_resp     UTL_HTTP.RESP;
    v_buffer   VARCHAR2(32767);
    v_response CLOB := EMPTY_CLOB();
BEGIN
    -- タイムアウトを設定する(接続タイムアウト・読み取りタイムアウト)
    UTL_HTTP.SET_TRANSFER_TIMEOUT(30);   -- 読み取りタイムアウト: 30 秒
    -- UTL_HTTP.SET_CONNECT_TIMEOUT(10);  -- 接続タイムアウト(Oracle 12.1.0.2+)

    -- リクエストを開始する
    v_req := UTL_HTTP.BEGIN_REQUEST(
        url    => 'https://jsonplaceholder.typicode.com/posts/1',
        method => 'GET',
        http_version => 'HTTP/1.1'
    );

    -- リクエストヘッダーを設定する
    UTL_HTTP.SET_HEADER(v_req, 'Accept',     'application/json');
    UTL_HTTP.SET_HEADER(v_req, 'User-Agent', 'Oracle-UTL_HTTP/12.2');

    -- レスポンスを取得する
    v_resp := UTL_HTTP.GET_RESPONSE(v_req);

    DBMS_OUTPUT.PUT_LINE('HTTP Status: ' || v_resp.status_code || ' ' || v_resp.reason_phrase);

    -- レスポンスボディを読み込む(32767バイトずつ)
    BEGIN
        LOOP
            UTL_HTTP.READ_TEXT(v_resp, v_buffer, 32767);
            v_response := v_response || v_buffer;
        END LOOP;
    EXCEPTION
        WHEN UTL_HTTP.END_OF_BODY THEN NULL;   -- ボディ末尾は END_OF_BODY 例外で検出する
    END;

    DBMS_OUTPUT.PUT_LINE('Response: ' || SUBSTR(v_response, 1, 500));

    -- レスポンスを必ず閉じる(ソケットのリークを防ぐ)
    UTL_HTTP.END_RESPONSE(v_resp);

EXCEPTION
    WHEN UTL_HTTP.TRANSFER_TIMEOUT THEN
        UTL_HTTP.END_RESPONSE(v_resp);
        DBMS_OUTPUT.PUT_LINE('タイムアウトが発生しました');
    WHEN OTHERS THEN
        -- 例外発生時もレスポンスを閉じる
        BEGIN UTL_HTTP.END_RESPONSE(v_resp); EXCEPTION WHEN OTHERS THEN NULL; END;
        RAISE;
END;
/

POST リクエストで JSON ボディを送信する

JSON ボディを持つ POST リクエスト
DECLARE
    v_req       UTL_HTTP.REQ;
    v_resp      UTL_HTTP.RESP;
    v_buffer    VARCHAR2(32767);
    v_response  VARCHAR2(32767);
    v_body      VARCHAR2(4000);
    v_body_raw  RAW(32767);
BEGIN
    -- 送信する JSON ボディを組み立てる
    v_body := '{"title":"Oracle Test","body":"Posted from PL/SQL","userId":1}';

    -- リクエストを開始する
    v_req := UTL_HTTP.BEGIN_REQUEST(
        url    => 'https://jsonplaceholder.typicode.com/posts',
        method => 'POST'
    );

    -- ヘッダーを設定する
    UTL_HTTP.SET_HEADER(v_req, 'Content-Type',   'application/json');
    UTL_HTTP.SET_HEADER(v_req, 'Accept',          'application/json');
    -- Content-Length は文字数でなくバイト数を指定する
    UTL_HTTP.SET_HEADER(v_req, 'Content-Length',
        TO_CHAR(LENGTHB(v_body)));

    -- ボディを送信する(文字列として送信)
    UTL_HTTP.WRITE_TEXT(v_req, v_body);

    -- レスポンスを取得する
    v_resp := UTL_HTTP.GET_RESPONSE(v_req);
    DBMS_OUTPUT.PUT_LINE('HTTP Status: ' || v_resp.status_code);

    BEGIN
        LOOP
            UTL_HTTP.READ_TEXT(v_resp, v_buffer, 32767);
            v_response := v_response || v_buffer;
        END LOOP;
    EXCEPTION
        WHEN UTL_HTTP.END_OF_BODY THEN NULL;
    END;

    DBMS_OUTPUT.PUT_LINE('Response: ' || v_response);
    UTL_HTTP.END_RESPONSE(v_resp);

EXCEPTION
    WHEN OTHERS THEN
        BEGIN UTL_HTTP.END_RESPONSE(v_resp); EXCEPTION WHEN OTHERS THEN NULL; END;
        RAISE;
END;
/

Authorization ヘッダーで API 認証する

Bearer トークン認証と Basic 認証の設定
DECLARE
    v_req    UTL_HTTP.REQ;
    v_resp   UTL_HTTP.RESP;
    v_buffer VARCHAR2(32767);
    v_token  VARCHAR2(500);
BEGIN
    v_token := 'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...';   -- JWT トークン

    v_req := UTL_HTTP.BEGIN_REQUEST(
        url    => 'https://api.example.com/v1/data',
        method => 'GET'
    );

    -- Bearer トークン認証
    UTL_HTTP.SET_HEADER(v_req, 'Authorization', 'Bearer ' || v_token);
    UTL_HTTP.SET_HEADER(v_req, 'Accept', 'application/json');

    v_resp := UTL_HTTP.GET_RESPONSE(v_req);
    DBMS_OUTPUT.PUT_LINE('Status: ' || v_resp.status_code);
    UTL_HTTP.END_RESPONSE(v_resp);
END;
/

-- Basic 認証の場合(ユーザー名:パスワードを Base64 エンコードする)
DECLARE
    v_req        UTL_HTTP.REQ;
    v_resp       UTL_HTTP.RESP;
    v_credential VARCHAR2(200);
    v_encoded    VARCHAR2(200);
BEGIN
    v_credential := 'api_user:secret_password';
    -- UTL_RAW.CAST_TO_RAW で文字列を RAW に変換してから Base64 エンコードする
    v_encoded := UTL_RAW.CAST_TO_VARCHAR2(
        UTL_ENCODE.BASE64_ENCODE(UTL_RAW.CAST_TO_RAW(v_credential))
    );

    v_req := UTL_HTTP.BEGIN_REQUEST('https://api.example.com/resource', 'GET');
    UTL_HTTP.SET_HEADER(v_req, 'Authorization', 'Basic ' || v_encoded);

    v_resp := UTL_HTTP.GET_RESPONSE(v_req);
    UTL_HTTP.END_RESPONSE(v_resp);
END;
/

HTTPS 接続と Oracle ウォレットの設定

HTTPS を使用する場合、Oracle は SSL/TLS 証明書の検証にOracle ウォレットを使用します。ウォレットに接続先の CA 証明書(ルート証明書)をインポートしていないと ORA-29024: Certificate validation failure が発生します。

HTTPS 接続時のウォレット設定
-- Oracle ウォレットを使った HTTPS 接続
-- ウォレットパス(orapki コマンドで作成・証明書をインポート済みのもの)
-- $ orapki wallet create -wallet /etc/oracle/wallet -auto_login
-- $ orapki wallet add -wallet /etc/oracle/wallet -trusted_cert -cert /path/to/ca.crt

-- PL/SQL でウォレットを指定して HTTPS に接続する
DECLARE
    v_req  UTL_HTTP.REQ;
    v_resp UTL_HTTP.RESP;
BEGIN
    -- ウォレットパスとパスワードを設定する(BEGIN_REQUEST の前に呼ぶ)
    UTL_HTTP.SET_WALLET(
        path     => 'file:/etc/oracle/wallet',   -- ウォレットのパス
        password => NULL                           -- auto_login ウォレットは NULL でよい
    );

    v_req  := UTL_HTTP.BEGIN_REQUEST('https://api.example.com/', 'GET');
    v_resp := UTL_HTTP.GET_RESPONSE(v_req);
    DBMS_OUTPUT.PUT_LINE('Status: ' || v_resp.status_code);
    UTL_HTTP.END_RESPONSE(v_resp);
EXCEPTION
    WHEN OTHERS THEN
        BEGIN UTL_HTTP.END_RESPONSE(v_resp); EXCEPTION WHEN OTHERS THEN NULL; END;
        RAISE;
END;
/

-- 証明書検証を無効化する(開発・テスト環境のみ)
-- 本番環境では必ずウォレットに証明書をインポートして検証を有効にすること
EXEC UTL_HTTP.SET_HTTPS_CERTIFICATES(NULL);  -- 検証無効(非推奨)

まとめ

  • 基本フロー:BEGIN_REQUEST → SET_HEADER → (POST の場合 WRITE_TEXT)→ GET_RESPONSE → READ_TEXT ループ(END_OF_BODY で終了)→ END_RESPONSE
  • END_RESPONSE は必須:例外発生時も必ず END_RESPONSE を呼ぶ。呼ばないとソケットがリークしてセッションリソースが枯渇する
  • ACL 設定:DBMS_NETWORK_ACL_ADMIN.APPEND_HOST_ACE で接続先ホストを許可リストに登録する。12c 以降は ACL ビューが DBA_HOST_ACES に統合された
  • HTTPS:Oracle ウォレットに CA 証明書をインポートして SET_WALLET でパスを指定する。証明書検証の無効化は本番 NG
  • タイムアウト:SET_TRANSFER_TIMEOUT で読み取りタイムアウトを必ず設定する。デフォルトは 60 秒

PL/SQL からファイルを操作する UTL_FILE については UTL_FILE完全ガイドを参照してください。DBMS_CRYPTO でデータを暗号化してから送信する場合は DBMS_CRYPTO完全ガイドも参照してください。