If you need to expose the data in your Oracle database to other systems in a standardized and technology-neutral way, web services are a natural choice. Broadly speaking, web services come in two flavors: SOAP and REST. Despite its name, the Simple Object Access Protocol (SOAP) can be complex and overkill for many common scenarios. Representational State Transfer (REST) is considered more lightweight and easy to use.
There is a good introduction to REST here: http://www.xfront.com/REST-Web-Services.html
A key point is that REST is not really a standard, but an architectural style. It is not limited to web services consumed by machines, either. Applications built using Ruby on Rails and the ASP.NET MVC framework typically have user-friendly URLs based on the REST principles.
For example, if you have been to StackOverflow.com, you will see URLs like the following, which are "clean" and friendly both to users and to search engines:
http://stackoverflow.com/users http://stackoverflow.com/users/1/jeff-atwood http://stackoverflow.com/questions http://stackoverflow.com/questions/tagged/oracle http://stackoverflow.com/questions/1078506/oracle-sql-developer-how-to-view-results-from-a-ref-cursor
So, REST is a good way of exposing resources on the web, and your Oracle database is full of resources (data), but how can you build a REST service using only PL/SQL?
The key to building a REST service in PL/SQL is in a documented, but little-used feature of the Embedded PL/SQL Gateway (and mod_plsql) called "Path Aliasing". I was not aware that this feature existed until I discovered it "by accident" while browsing the mod_plsql documentation:
http://download.oracle.com/docs/cd/A97335_02/apps.102/a90099/feature.htm#1007126
"If the PL/SQL Gateway encounters in an incoming URL the keyword entered in the Path Alias field, it invokes the procedure entered in the Path Alias Procedure field. (...) Applications that use path aliasing must implement the Path Alias Procedure. The procedure receives the rest of the URL (path_alias_URL) after the keyword, URL, as a single parameter, and is therefore responsible and also fully capable of dereferencing the object from the URL. Although there is no restriction on the name and location for this procedure, it can accept only a single parameter, p_path, with the datatype varchar2."
Sounds good, so let's try it out. First, we need to configure the Database Access Descriptor (DAD) to define a PL/SQL procedure which will handle our REST requests. Using the embedded gateway (DBMS_EPG), the attributes are called "path-alias" and "path-alias-procedure" (the corresponding DAD attributes for mod_plsql are "PlsqlPathAlias" and "PlsqlPathAliasProcedure").
I will be using the embedded gateway for this example. Assuming you have an existing DAD called "devtest", run the following as user SYS (or another user who has the privileges to modify the EPG configuration).
begin dbms_epg.set_dad_attribute (dad_name => 'devtest', attr_name => 'path-alias', attr_value => 'rest-demo'); dbms_epg.set_dad_attribute (dad_name => 'devtest', attr_name => 'path-alias-procedure', attr_value => 'rest_handler.handle_request'); end; /
Then we need to create the procedure itself. Run the following in the schema associated with the DAD:
create or replace package rest_handler as /* Purpose: A simple example of RESTful web services with PL/SQL (see http://en.wikipedia.org/wiki/Representational_State_Transfer#RESTful_web_services) Remarks: The DAD must be configured to use a path-alias and path-alias-procedure Who Date Description ------ ---------- -------------------------------- MBR 05.07.2009 Created */ -- the main procedure that will handle all incoming requests procedure handle_request (p_path in varchar2); end rest_handler; /
And then the package body (the example assumes that the EMP and DEPT demo tables exist in your schema; if not, then modify the code accordingly):
create or replace package body rest_handler as /* Purpose: A simple example of RESTful web services with PL/SQL (see http://en.wikipedia.org/wiki/Representational_State_Transfer#RESTful_web_services) Remarks: The DAD must be configured to use a path-alias and path-alias-procedure Who Date Description ------ ---------- -------------------------------- MBR 05.07.2009 Created */ g_request_method_get constant varchar2(10) := 'GET'; g_request_method_post constant varchar2(10) := 'POST'; g_request_method_put constant varchar2(10) := 'PUT'; g_request_method_delete constant varchar2(10) := 'DELETE'; g_resource_type_employees constant varchar2(255) := 'employees'; g_resource_type_departments constant varchar2(255) := 'departments'; procedure handle_emp (p_request_method in varchar2, p_id in number) as begin /* Purpose: Specific handler for Employees Remarks: Who Date Description ------ ---------- -------------------------------- MBR 05.07.2009 Created */ if (p_id is not null) then if p_request_method = g_request_method_delete then delete from emp where empno = p_id; elsif p_request_method = g_request_method_get then for l_rec in (select * from emp where empno = p_id) loop htp.p(l_rec.empno || ';' || l_rec.ename || ';' || l_rec.sal); end loop; end if; else if p_request_method = g_request_method_get then for l_rec in (select * from emp order by empno) loop htp.p(l_rec.empno || ';' || l_rec.ename || ';' || l_rec.sal || ' '); end loop; end if; end if; end handle_emp; procedure handle_dept (p_request_method in varchar2, p_id in number) as begin /* Purpose: Specific handler for Departments Remarks: Who Date Description ------ ---------- -------------------------------- MBR 05.07.2009 Created */ if (p_id is not null) then if p_request_method = g_request_method_delete then delete from dept where deptno = p_id; elsif p_request_method = g_request_method_get then for l_rec in (select * from dept where deptno = p_id) loop htp.p(l_rec.deptno || ';' || l_rec.dname || ';' || l_rec.loc); end loop; end if; else if p_request_method = g_request_method_get then for l_rec in (select * from dept order by deptno) loop htp.p(l_rec.deptno || ';' || l_rec.dname || ';' || l_rec.loc || ' '); end loop; end if; end if; end handle_dept; procedure handle_request (p_path in varchar2) as l_request_method constant varchar2(10) := owa_util.get_cgi_env('REQUEST_METHOD'); l_path_elements apex_application_global.vc_arr2; l_resource varchar2(2000); l_id number; begin /* Purpose: The main procedure that will handle all incoming requests Remarks: Parses the incoming path and calls a specific handler for each resource type Who Date Description ------ ---------- -------------------------------- MBR 05.07.2009 Created */ -- note that an extra delimiter is added to the path, in case the user leaves out the trailing slash l_path_elements := apex_util.string_to_table (p_path || '/', '/'); begin l_resource := l_path_elements(1); l_id := l_path_elements(2); exception when value_error or no_data_found then l_resource := null; l_id := null; end; case lower(l_resource) when g_resource_type_employees then handle_emp (l_request_method, l_id); when g_resource_type_departments then handle_dept (l_request_method, l_id); when 'apex-employees' then -- we can also use this REST handler to make pretty, search-engine-friendly URLs for Apex applications without having to use Apache mod_rewrite apex_application.g_flow_id := 104; owa_util.redirect_url('http://127.0.0.1:8080/apex/f?p=104:2:' || apex_custom_auth.get_session_id_from_cookie || '::::P2_EMPNO:' || l_id, true); else owa_util.status_line(404, 'Resource type not recognized.', true); end case; end handle_request; end rest_handler; /
Now try the following URLs in your browser, and you should be able to see the familiar EMP and DEPT data, in all their RESTful glory! Note that for simplicity, the example code produces simple semicolon-separated values, but depending on your requirements and who will consume the service (machine or human), you will probably want to use XML or HTML as the output format.
http://127.0.0.1:8080/devtest/rest-demo/departments http://127.0.0.1:8080/devtest/rest-demo/departments/10 http://127.0.0.1:8080/devtest/rest-demo/employees http://127.0.0.1:8080/devtest/rest-demo/employees/7839
Bonus: Making the URLs in your Apex applications pretty
The URLs generated by Apex are not very friendly to users nor to search engines. You can use Apache with mod_rewrite to set up mapping between REST-style URLs to your Apex pages, but the example code above also shows how this can be accomplished using pure PL/SQL.
The example assumes that you have an Apex application with Application ID = 104, and that you have made a Form on the EMP table on Page 2 of the application.
Try the following link
http://127.0.0.1:8080/devtest/rest-demo/apex-employees/7839
and you should be redirected to the Apex application (and if you were already logged into the Apex application, you don't have to login again as it will reuse your existing session).
REST for inserts and updates
The four HTTP methods are GET, PUT, POST and DELETE. To create a REST web service that can update data as well as query it, we need to inspect the CGI environment variable REQUEST_METHOD and process the request accordingly (see the Wikipedia article for details). The example code implements the GET and DELETE methods for Employees and Departments.
However, I don't see how we can implement POST or PUT processing using the PathAlias technique. The problem is that the webserver/gateway only sends the URL to the PathAliasProcedure. Any data that is POSTed to the URL is simply discarded by the gateway. Ideally, the names and values of the request should be sent to the PathAliasProcedure in name/value arrays (just like the gateway does when using flexible parameter passing). If anyone from Oracle is reading this, it can be considered an enhancement request for the next version of mod_plsql and the embedded gateway!
Even with this limitation, the ability to expose (read-only) data from the database as RESTful web services using just PL/SQL is pretty cool, isn't it? :-)
33 comments:
Fantastic. I have wanted to know how to do this for so long. Good spot on the doccos. ;-)
Cool :)
well done
I'm not sure I'm following where the package gets created... I've tried it in several schemas but I get the following:
Tue, 22 Feb 2011 15:15:50 GMT
Failed to parse target procedure
myName: PROCEDURE DOESN'T EXIST
DAD name: apex
PROCEDURE : myName
URL : http://1.1.1.1:4443/pls/apex/myName
PARAMETERS :
@Tim: You need to set the "path-alias" parameter to the virtual path where you want to accept requests. If you set this to (for example) "my_rest_path", then you can invoke your "path-alias-procedure" using the following syntax:
http://1.1.1.1:4443/pls/apex/my_rest_path/this_will_get_passed_to_the_path_alias_procedure
- Morten
I'm still missing something, not sure what but maybe this will help...
Here are the DAD Attributes that are set:
path-alias:my_rest_path
path-alias-procedure:rest_handler.handle_request
and when I try to go to it, I get the following:
Failed to parse target procedure
my_rest_path: PROCEDURE DOESN'T EXIST
DAD name: apex
PROCEDURE : my_rest_path
URL : http://1.1.1.1:4443/pls/apex/my_rest_path/thisisatest
PARAMETERS :
===========
@Tim: What web server are you using? Mod_plsql or DBMS_EPG?
Quoting from my original blog post:
Using the embedded gateway (DBMS_EPG), the attributes are called "path-alias" and "path-alias-procedure" (the corresponding DAD attributes for mod_plsql are "PlsqlPathAlias" and "PlsqlPathAliasProcedure").
From the "pls" part of your URL, it seems you are using mod_plsql, so then you should use "PlsqlPathAlias" instead of "path-alias".
- Morten
So, then I have to put it directly into the dads.conf?
For some reason I didn't read that correctly... I'll give it a try.
I'm a little confused at the applicability of this using a HTTP Server such as Apache.
I'm using Glassfish to serve an 11g instance on my laptop with Apex4.
My typical URL would start as
http://localhost:8080/apex40/f?p=
However when I try to set the dad attributes, I receive this error:
1 begin
2 dbms_epg.set_dad_attribute
3 (dad_name => 'apex40'
4 ,attr_name => 'PlsqlPathAlias'
5 ,attr_value => 'sage');
6* end;
SQL> /
begin
*
ERROR at line 1:
ORA-24231: database access descriptor (DAD) apex40 not found
ORA-06512: at "SYS.DBMS_SYS_ERROR", line 86
ORA-06512: at "SYS.DBMS_EPG", line 67
ORA-06512: at "SYS.DBMS_EPG", line 274
ORA-06512: at line 2
Any ideas?
@Scott: DBMS_EPG is, as the name implies, used to configure the Embedded PL/SQL Gateway (EPG).
If you use Apache with mod_plsql, configuration is done using the .conf file.
If you use the Java-based Apex Listener, then configuration is done using the built-in administration interface. However, I do not believe that the Apex Listener supports the PathAliasProcedure feature (at least not yet -- I suggest you submit an enhancement request to Oracle if you need it).
- Morten
Thanks Morten - I was sure that was the case, but I thought maybe I was reading it wrong.
I'll have a play with my Glassfish laptop - but when I define our EC2 instance we will have flexibility.
Thanks - this information is great.
Also for Mod_plsql, I also had to add the following to the dads.conf:
PlsqlRequestValidationFunction wwv_flow_epg_include_modules.authorize
Allow from all
Also had to add this function to get rid of an error I was getting...
-- "Set define off" turns off substitution variables.
Set define off;
CREATE OR REPLACE FUNCTION APEX_030200.wwv_flow_epg_include_mod_local(
procedure_name in varchar2)
return boolean
is
begin
--return false; -- remove this statement when you modify this function
--
-- Administrator note: the procedure_name input parameter may be in the format:
--
-- procedure
-- schema.procedure
-- package.procedure
-- schema.package.procedure
--
-- If the expected input parameter is a procedure name only, the IN list code shown below
-- can be modified to itemize the expected procedure names. Otherwise you must parse the
-- procedure_name parameter and replace the simple code below with code that will evaluate
-- all of the cases listed above.
--
if upper(procedure_name) in (
'REST_HANDLER.HANDLE_REQUEST') then
return TRUE;
else
return FALSE;
end if;
end wwv_flow_epg_include_mod_local;
/
Hello Oracle Gurus,
Pardon me for my ignorance. I am new to this Web Services thing. I am trying to create a REST WS which handles data in JSON format.
I went through this page. Very interesting but seems pretty complicated to me.
I also bumped into this page http://blog.warp11.nl/2011/07/calling-a-rest-webservice-with-plsql/
which looks very simple.
So, what's the difference between these 2 approaches??? Why is one very long than the other??
Any advantages / disadvantages with these 2 methods (i.e. one over the other)???
@Rohitha: The link you provided is an example of calling (or "consuming") a REST web service on an external server from PL/SQL.
This blog post is about creating (or "exposing") a REST web service from PL/SQL. It's the opposite of calling a service.
Also, the meat of the example is really the handle_request procedure, which is just a dozen or so lines of code.
- Morten
Great writeup. I have this working as you described. I was wondering if you had any updates on using the POST method. I can currently only post if I include p_path= in the body and it essentially ignores my URL after the alias. Ideally it would use the URL and also allow an XML document in the body without the p_path=.
@Mike: As mentioned at the end of the blog post, "The problem is that the webserver/gateway only sends the URL to the PathAliasProcedure. Any data that is POSTed to the URL is simply discarded by the gateway. Ideally, the names and values of the request should be sent to the PathAliasProcedure in name/value arrays (just like the gateway does when using flexible parameter passing). If anyone from Oracle is reading this, it can be considered an enhancement request for the next version of mod_plsql and the embedded gateway!"
To my knowledge there has been no change in this regard for mod_plsql (and I haven't checked if/how the Apex Listener handles this), but I did add support for this in my own Thoth Gateway (a mod_plsql port for IIS).
See http://code.google.com/p/thoth-gateway/ and the section called "Features in Thoth that are not in mod_plsql".
- Morten
Hello Morten,
1. Thank you for this great Blog.
2. Small comment (I am not sure about it but it seems like) : Google recognizes this as a redirect call to a different URL (in our case apex application) and lower ranking.
3. I have tried once to implement the following mod_rewrite code, which was very good , except some notification message : https://forums.oracle.com/forums/thread.jspa?threadID=299891&tstart=0&start=15
4. So my questions are :
a. Referring to comment mentioned in point 2.
b. Are you familiar with any intentions from Oracle to allow native Url friendly built in option ?
Much appreciated.
Regards
Etay G
begin
dbms_epg.set_dad_attribute (dad_name => 'devtest', attr_name => 'path-alias', attr_value => 'rest-demo');
dbms_epg.set_dad_attribute (dad_name => 'devtest', attr_name => 'path-alias-procedure', attr_value => 'rest_handler.handle_request');
end;
/
What does this mean if I am running multiple applications?
How do i need to configure the DAD settings so i will get pretty URLs for all applications ?
Do i have to add a line
dbms_epg.set_dad_attribute (dad_name => 'devtest', attr_name => 'path-alias', attr_value => 'rest-demo');
for all applications??
what does rest-demo in this example mean?
@Anonymous: "rest-demo" is the virtual folder name that tells the gateway to forward the request to your handler procedure instead of executing the normal procedure.
"rest_handler.handle_request" is the name of the procedure that will get called.
You can change the names of both of those attributes (making sure to change your code accordingly).
Have a look at the first screenshot in the blog post. The url is
http://127.0.0.1:8080/devtest/rest-demo/employees/7839
In that example, "devtest" is the DAD, "rest-demo" is the prefix that tells the gateway to forward everything after that prefix to the handler procedure, and "employees/7839" is what will actually get forwarded to the handler procedure.
In your code (in the handler procedure) you can parse this string and do anything you like with it. You can make it handle multiple Apex applications if you want.
- Morten
Hi Morten,
Do you think this kind of solution works with the latest version of APEX Listener?
Regards,
Learco
@Learco: It depends on whether the Apex Listener supports the "PathAlias" and "PathAliasProcedure" configuration parameters. I don't think it does.
However, since the Apex Listener now comes with extensive support for RESTful Web Services, you don't need the PathAliasProcedure for that anymore. Rewriting the URL is another matter, perhaps you can set up a RESTful service in the Apex Listener that executes PL/SQL that issues a redirect to the browser?
- Morten
Thanks for sharing this new concept.
I tried to run the same procedure with some table name change and iam able to compile all the procedures without any errors.
When i try to run the URL i am getting page not found error.
I have few questions for you.
1. Do i need to have APEX installed ?
2. I am running all these under system schema and using XE database.
Thanks,
Vijay
Hi,
How can make it work without using APEX?
Thanks,
Vijay
@Vijay:
1. No, you don't need Apex, this works just using the PL/SQL Web Toolkit (OWA) and the Embedded PL/SQL Gateway (DBMS_EPG).
2. You should use the SYS user to set up the DAD settings, but use another schema (I've used a schema called "devtest") to install your PL/SQL code.
To troubleshoot the "page not found" errors, you should enable the DebugStyle error reporting. See http://daust.blogspot.com/2008/04/troubleshooting-404-not-found-error-on.html for details.
- Morten
Hi,
Is it possible to use DAD's parameters:
'PlsqlDocumentTablename',
'PlsqlDocumentPath', ...
for POST,PUT methods?
Thanks,
Mikle
@Mikle: As described in the last part of my post, this will not work for PUT and POST requests, since mod_plsql does not send the posted data to the PathAliasProcedure.
If you use my Thoth Gateway (mod_plsql for IIS), there is a configuration option you can enable to get access to the POST data.
And of course, if you use Oracle's Apex Listener with Apex 4.2, you can use true RESTful Services directly via the listener.
- Morten
The link for documentation refers to Oracle 9i. Has anything changed ( advanced) in Oracle 11g? I am on 11g release 2 ( 11.2.0.3)
Often, I read "how call web service with pl/sql", but never "how extract parameters...".
I created the restful web service with pl/sql and embededd gateway apex, and i need to extract parameter written via UTL_HTTP.WRITE_TEXT....
Can you suggest me ?
Thanks for this example Morten - looks excellent and it works. What happens if you want to make it https (as opposed to http). What changes does one need to make? I'm on Oracle 11.2.0.4
Thanks
@Chopped4Skin: If you are using the Embedded PL/SQL Gateway (DBMS_EPG), that does not support https as far as I know.
If you are using Apache and mod_plsql, then you would just set up an SSL certificate as normal, no changes to the PL/SQL code.
- Morten
I found out that POST variables are mapped to input parameters in handle_request.
Example:
curl -s -k -D- -X POST --data 'a=b' URL
You use procedure handle_request(p_path IN VARCHAR2 DEFAULT NULL, p_json_payload in varchar2 default '{}')
Hi,
I tried to follow your example to create a plsql REST webservice routine that select all tables from user_tables and generates a webservice to each table using, by example:
SELECT DBURIType(''/HR/DEPARTMENTS').getXML() FROM DUAL
but it didn't work.
Do you have any suggestion or one example in this direction?
Post a Comment