Make a REST call with JSON content to a HTTPS webservice from PL/SQL 4
All keywords are now in the title. 🙂
I’ve started developing this small PL/SQL procedure based on Lucas Jelema blog entry Invoke a REST service from PL/SQL – make an HTTP POST request using UTL_HTTP in Oracle Database 11g XE .
Unfortunately it took me a while to find out why I was getting the error:
<!DOCTYPE HTML PUBLIC "-//IETF//DTD HTML 2.0//EN">
<html><head>
<title>400 Bad Request</title>
</head><body>
<h1>Bad Request</h1>
<p>Your browser sent a request that this server could not understand.<br />
</p>
</body></html>
There is a empty space at the beginning of a variable in the example on Lucas’ blog post – before the HTTP/1.1 on the line below:
req := utl_http.begin_request(url, 'POST',' HTTP/1.1');
My need is a bit more complex, so I leave here the complete code.
Certificate and Oracle wallet
First you need to download the certificate. A good tutorial do to this is at Shellhacks.com.
Then you need to create a wallet on the database server and add the certificate:
mkdir ${ORACLE_BASE}/admin/${ORACLE_SID}/wallet
orapki wallet create -wallet ${ORACLE_BASE}/admin/${ORACLE_SID}/wallet -pwd WalletPassword123 -auto_login
orapki wallet add -wallet ${ORACLE_BASE}/admin/${ORACLE_SID}/wallet -trusted_cert -cert "/tmp/my_certificate.crt" -pwd WalletPassword123
Oracle ACLs
On the database (my case was Oracle 11.2) you need to have XDB installed and add the ACLs. I skip the instructions to install XDB. The user is called GREEN.
grant execute on utl_http to GREEN;
grant execute on dbms_lock to GREEN;
BEGIN
DBMS_NETWORK_ACL_ADMIN.create_acl (
acl => 'acl_file.xml',
description => 'Allows sending alerts to REST service',
principal => 'GREEN',
is_grant => TRUE,
privilege => 'connect',
start_date => SYSTIMESTAMP,
end_date => NULL);
end;
/
begin
DBMS_NETWORK_ACL_ADMIN.assign_acl (
acl => 'acl_file.xml',
host => 'my_rest_service.com',
lower_port => 443,
upper_port => NULL);
end;
/
Procedure to invoke REST service
The rest service requires a username password. I use the encoded version, so there are no visible passwords in the code. You can encode the pair username:password here.
CREATE OR REPLACE PROCEDURE green.publish_event
( p_robot in varchar2, p_business_service in varchar2, p_target_ci in varchar2, p_transaction in varchar2, p_status in number, p_message in varchar2) is
req utl_http.req;
res utl_http.resp;
url varchar2(4000) := 'https://my_rest_service.com/api/event';
buffer varchar2(32767);
content varchar2(8000) := '{ "robot":"'||p_robot||'", "business_service":"'||p_business_service||'", "target_ci":"'||p_target_ci||'", "transaction":"'||p_transaction||'", "status":"'||p_status||'", "message":"'||p_message||'", "target_support_group": "", "organization": "ANJO_EVENT"}';
BEGIN
utl_http.set_wallet('file:${ORACLE_BASE}/admin/${ORACLE_SID}/wallet', null);
req :=UTL_HTTP.begin_request(url, 'POST');
utl_http.set_header(req, 'Authorization', 'Basic f24iYsBpdGVzdDp3ZWJhcGl0ZXN0');
utl_http.set_header(req, 'Content-Length', length(content));
utl_http.write_text(req, content);
res := utl_http.get_response(req);
-- process the response from the HTTP call
begin
loop
utl_http.read_text(res, buffer,32767);
dbms_output.put_line(buffer);
end loop;
utl_http.end_response(res);
exception
when utl_http.end_of_body
then
utl_http.end_response(res);
end;
END publish_event;
/
Trigger to automatically send event
In my case, the idea is to send an event every time a line is inserted into a table. So I just create a small trigger that calls the procedure above.
CREATE OR REPLACE TRIGGER green.trg_table1_monitor
AFTER INSERT
ON green.table1_monitor
FOR EACH ROW
BEGIN
publish_event (
p_robot => :new.host_name,
p_business_service => 'IT DB',
p_target_ci => '356224bs5b1f6f401d2262eb0b9619ad',
p_transaction => 'Table1',
p_status => 7,
p_message => 'Green-Table1 -Exception: '|| substr(:new.exception,0,100)
);
END;
/
Et voilà !