human readable URLs in Oracle APEX – Part 3

In the first two parts of this blogpost series we covered the basics and the technique on how to provide nice/friendly/readable URLs for a public APEX Application.

If you haven’t read the other two parts, please go back and read part 1, then read part 2. Done? Ok, lets continue.

Again here the links for two examples: the Sample Database Application which will be used as example in this post and the click-click Website (which is multilingual, just change /en/ to /de/ in the URL).

This Blogpost will cover the necessary changes in an APEX Application, to work with human readable URLs.

Public Pages

Really important is that all pages in your application are set to “Public”

and that the active Authentication Scheme isn’t No Authentication (using DAD), since that would lead to problems in combination with the APEX Listener with APEX 4.2.3 and below (Bug 17796199 fixed in 4.2.4).

Cookie

In the Authorization Scheme go to the cookie section and set a cookie path, matching your base URL. On apex.oracle.com this is /pls/apex/, on the click-click Website it is /web/.

The Path is important, otherwise a new cookie (and new APEX session) would be created when visiting /home at first and /customers after that.

The Cookie Name is optional, you can also leave that blank.

Target is URL

Above we prepared the application to support our new URL structure. Now its time to walk through our application and change every Button, List Item, Branch and whatever Link you may have to use the new and shiny URLs.

A good idea here is to define an application substitution string which holds the base URL. In my demo I have defined a substitution string called G_URL which is set to /pls/apex/hrurl. In URL Targets I then can use it like this: &G_URL./customer/#CUSTOMER_ID#

Base URL to support Form Submits

So far everything works fine, we can call and show all our Pages. But when trying to submit (save) a form we get an error saying that wwv_flow.accept doesn’t exist.

This happens, because wwv_flow.accept is simply appended to the end of the active URL. e.g. http://apex.oracle.com/pls/apex/hrurl/customer/2/wwv_flow.accept

The easy workaround is to define a Base URL using a simple HTML Meta Definition.

Can you see it? It is this simple command: <base href=”/pls/apex/” />

UPDATE: older IE versions (yes, some people still call it a browser) need a fully referenced base href, like: <base href=”http://apex.oracle.com/pls/apex/” />

Now wwv_flow.accept is appended to the base URL and all submits work fine again.

Would be great, if the APEX Dev Team could include the base-URL definition automatically.

AJAX Calls and Interactive Reports

There is still a tiny problem with our friendly URLs. AJAX calls (also Interactive Reports and regular report pagination) don’t work anymore. The AJAX calls are returned with a HTML error code.

To find the reason for that I had to do a code-review of the APEX Javascript AJAX functions and found a Bug in htmldb_Get, which is the underlying function of many AJAX calls in APEX.

Problem here is htmldb_Get doesn’t use the base-URL, it tries to find f?p in the current URL and replaces that with wwv_flow.show (the AJAX Entrypoint). Our URLs don’t show f?p, so wwv_flow.show is appended to the end of the current URL, which is obviously wrong.

This Bug still exists in APEX 4.2.4, I hope it will get fixed in APEX 5.0.

There is no easy workaround here. The only option we have is to overload and redefine htmldb_Get, which I did for the demos.

Since this code is very version dependent and over 200 lines of code, I decided not to share it in this blog. If you know your way around javascript you’ll be able to get it from the demo. Otherwise you should keep your hands off.

Again: overwriting htmldb_Get is a workaround which is likely to break with the next APEX Version.

Summary

Using friendly URLs is possible today, even in older APEX Versions when using mod_plsql. There are some things you need to take care of in your application, as listed above.

Once you know your way around, it takes a couple hours to turn an existing public application into using human readable URLs.

Enjoy!

17 thoughts on “human readable URLs in Oracle APEX – Part 3

  1. Thanks for sharing this info in such comprehensive posts. I’ve used Apache rewrite rules, but this looks way better.

  2. As you stated: it only works for public applications and that is a big limitation, I think.
    Most applications will have some authentication and authorisation.

    How would you solve this matter?
    Is this something the APEX team should try to find a solutions for?

    I hope you can persuade the APEX team to have a look at it, because it is something a lot of developers would like to use.

  3. I also stated, that it most likely isn’t important to have nice URLs for internal applications.

    Saying that, I can live with this workaround for the time being, but would prefer an integrated solution delivered by APEX itself.

  4. The real good thing about the nice URLs is that it doesn’t include the application ID.
    Users can bookmark the URL of their internal application(s).
    This means you always have to keep the same application ID.
    When the application is installed under a new application ID, all users have to change their bookmarks.

    It could be avoided by making a portal.
    But still linking between two applications also means that the calling application has to be changed when the called application gets a new application ID.

    The elegance of your solution is that the used application ID’s got be stored in a table.
    That means you only have to maintain it on one place. Much more robust.

  5. Hi Peter,

    I m using your given method to get a nicer url, i have implemented the steps you mentioned above.

    1. Setting all application pages authentication to public.
    2. Setting Cookie Path attributes to /pls/apex/ as mentioned above.
    3. Further i have replaced all targets to URL according to my page name along with cookie path.

    I m getting the following error while navigating to the page.

    Http Status 404- Not Found.
    type Status report
    messageNot Found
    descriptionThe requested resource is not available.
    GlassFish Server Open Source Edition 4.0

    Please help to solve the problem.

    Regards
    Faraz Saleem

  6. HI Peter,

    I m using Oracle Glassfish server as application server , i want to set the following parameter for it

    PlsqlPathAlias ragtest
    PlsqlPathAliasProcedure rag_url_mapping

    I m not finding the dads.conf in it.
    Please help.

    Regards

  7. That’s ok, it means your server is set up correctly.

    The dads.conf exists only for mod_plsql which requires the Oracle Application Server.

    I assume you are using the APEX Listener (Oracle REST Data Services) ? In that case you want to use the RESTful Webservices method.

  8. Hi Peter,

    I have set the following cookie path parameter.

    Cookie Settings:
    My Actual Application URL: MyServer:Port/ords/
    Cookie Path: /ords/
    is it correct?.

    In the second part of your post you created a Procedure URL_DEMO with path parameter, i m not finding this procedure any where in the post for use, i mean where are we using the procedure URL_DEMO.

    Secondly, I m not getting what is hrurl in the link /pls/apex/hrurl/orders mentioned for target, and where are we using the web service in this example.

    Regards

  9. Hey Peter,

    Below are the steps, i m using for Nicer URL example.

    1. Created a procedure URL_DEMO.

    create or replace PROCEDURE URL_DEMO( p_path IN VARCHAR2)
    AS
    v_path APEX_APPLICATION_GLOBAL.VC_ARR2;
    BEGIN
    v_path := APEX_UTIL.STRING_TO_TABLE(p_path||’/’, ‘/’);

    IF UPPER(v_path(1)) = ‘HOME’
    THEN
    F(P=>’162:1:0′);
    ELSIF UPPER(v_path(1)) = ‘COMPANY’
    THEN
    F(P=>’162:2:0::YES’);
    ELSE
    HTP.P(‘URL_DEMO called!’);
    HTP.P(‘…path = ‘||p_path);
    END IF;
    END;

    2. Created a RESTFul Web service , with the given settings.

    RESTful Service Module
    Name: HRUrl
    Pagination Size:25
    Status:Published

    Resource Template
    RESTful Service Module: HRUrl
    URI Template:{path}
    Priority:0
    Entity Tag:Secure Hash

    Resource Handler
    URI Template: {path}
    Method :GET
    Source Type:PL/SQL
    Source: BEGIN url_demo(:path); END;

    3. Setting Authentication Scheme and Cookie Path.
    Scheme Type: Application Express Accounts

    Session Cookie Attributes
    Cookie Path:/ords/

    4. Created two public pages.
    1.HOME
    2.CUSTOMER

    5. Created a List for Navigation between pages.
    List Entries:
    1. Home
    Target
    Target type:URL
    URL Target:/ords/HRUrl/HOME/
    2. Company
    Target
    Target type:URL
    URL Target:/ords/HRUrl/COMPANY/

    6.Created a zero page to display the Navigation List in it.

    Still getting the error 404- Not Found with the given URL http://host:port/ords/HOME/.

    Can you tell me which is the step i m missing.
    looking for your response.

    Regards

  10. Hi Peter,

    Thanks for this really helpful article. I followed the steps and it works perfectly for me. I am using ORDS in standalone mode and I am trying to achieve a more nice URL without the “/web/” part. What I did, I set up an NGINX web server in front of the ORDS. The NGINX listens on port 80 and proxyes everything to the ORDS listening on 8080.
    Everything works fine except submitting a page (wwv_flow.accept).
    You can check it here:
    Original using your solution: http://87.229.23.171:8080/web/tsmt/home
    using nginx proxy: http://87.229.23.171
    In the left menu “Állapotfelmérés” (http://87.229.23.171/allapotfelmeres) has a submit button, but it redirects to a wrong URL. It works fine if you try it using directly the ORDS (http://87.229.23.171:8080/web/tsmt/allapotfelmeres).
    My nginx config is this:
    server {
    listen 80 default_server;
    location /i/ {
    root /u01/app/oracle/product/ords/images/;
    rewrite ^/i/(.*)$ /$1 break;
    break;
    }

    location / {
    proxy_set_header X-Real-IP $remote_addr;
    proxy_buffering off;
    proxy_set_header Host $host;
    proxy_set_header X-Forwarded-For $remote_addr;
    proxy_pass http://localhost:8080;
    rewrite ^/$ /home redirect;
    rewrite ^/(.*)$ /web/tsmt/$1 break;
    }
    }
    Please advise. Thank you in advance.
    Regards

  11. Hi Balazs,

    sorry, I don’t know about Nginx config, but I would be interested to hear your solution once you found the problem.

  12. Peter,

    Does this approach not change with the 5.0 page (and security) setting “Rejoin Sessions” ?

    The reason I ask is that I’m building Apex 5.0 application that shows a user’s FlickR photos and videos.

    This requires setting up Oauth, which involves going to the Flickr authorize page where the user agrees to allow the Apex application to access private photos.

    I read your excellent tutorial but found that this “rejoin sessions” setting allows me to effectively rejoin the same Apex session which makes the initial Oauth token request; I’m passing to FlickR a callback URL which is actually a RESTful service designed much like you describe – the only difference is that the plsql rejoins the original session without having to set session_id to “0”. The page I’m displaying from the plsql requires authentication but is rejoined without the user having to login again.

    Many thanks again for excellent ideas in this tutorial. Was very helpful.
    Mark.

Leave a Reply

Your email address will not be published. Required fields are marked *