Saturday, January 16, 2016

Automatic ADF Logout on Browser Close with WebSocket

Every ADF project could have a requirement to handle browser close event effectively. Differently than desktop applications where we could handle such events, browser doesn't send any event to the server, when browser page is closed. This is especially important for transactional data, when user locks data row and lock must be released automatically, in case if user is closing browser without unlocking. Besides transactional data management, it is important for performance improvement - Web session will be closed instantly and WebLogic resources will be released. There was no reliable solution to handle this, now we can do it with WebLogic 12c and WebSockets. WebLogic 12c supports WebSockets natively, there is no need to configure anything or add libraries.

When WebSocket channel is closed, it delivers event to the server on browser close. We could use this event to release locked resources, we need to logout ADF session to force ROLLBACK. Sample application implements HTTP client invoked by WebSocket server endpoint. This HTTP client simulates browser activity and redirects to adfAuthentication with logout request, using original JSESSION ID from closed browser session. This works also during accidental client computer power off. Download sample application where I have described all important implementation steps - ADFSessionHandlingWebSocket.zip.

Sample application is protected with ADF Security, you can login with redsam/welcome1 user. It includes Oracle JET libraries, one of the dashboard tiles implements JET fragment. JET is not required for the sample to work, it is used only to implement dashboard UI.

You can observe from browser log when WebSocket connection is opened. Connection is established on initial page load, immediatelly after login. This opens WebSocket communication chanel, as soon as this chanel will be closed - WebSocket server endpoint will force logout for ADF session from closed browser:


As soon as WebSocket channel is established, ADF web session ID (JSESSIONID is retrieved from the cookie) is sent to WebSocket server endpoint. Sample logs ID of ADF web session:


I'm going to lock one of the records, I'm invoking ADF BC lock method (DB pool is disabled, this will keep DB connection assigned for AM):


Lock action for row data is visible in the log, SELECT FOR UPDATE is executed for ID 102:


Let's close a browser now without issuing ROLLBACK, invoke Quit action in the browser:


WebSocket channel will be closed and this will trigger ADF Authentication servlet request to logout from ADF web session. As logout happens, ADF resources are released, ADF BC is triggering ROLLBACK in DB:


Session is closed based on JSESSIONID. With HTTP Client we simulate user logout after browser was closed:


Now we should overview technical implementation. WebSocket channel is opened from JavaScript:


This happens on page load, with clientListener triggering connectSocket method:


Method connectSocket in JavaScript is using standard WebSocket API to open connection:


WebSocket server endpoint is defined with custom configurator. Through configurator we can reference HTTP session initiated in ADF. HTTP client in WebSocket endpoint will use it later, to simulate ADF logout:


ADF HTTP session is referenced through WebSocket configurator:


Helper session listener class is defined in web.xml, here I'm copying JSESSIONID from the cookie into session attribute (to be able to reference JSESSIONID in WebSocket endpoint):


OnClose method in WebSocket endpoint is invoked, when connection channel between client and server is closed (browser is closed). Here I'm invoking custom method handleLogout method:


I'm constructing HTTP client request with the same JSESSIONID value as it was copied from ADF session. HTTP Get is executed against ADF Authentication servlet with logout request using JSESSIONID value. This is forcing ADF session to logout from simulated HTTP session by HTTP client:


18 comments:

tarek fathy said...

Wonderful. This post saved me. Thank you so much

Stefan du Preez said...

Hi Andrejus,

I want to take a more generic approach with this, because we have multiple application servers, hard coding die logout url will not work. So i am trying to understand the process from beginning to end. In the websocket.js on the close event you call closeSession(), where is this method or from what library does it originate?

Thank you

Andrejus Baranovskis said...

Hi,

closeSession() method is needed. WebSocket closing connection by itself. Yes, you need to pass extra information to WebSocket, to make it generic logout. This is just concept example.

Regards,
Andrejus

Stefan du Preez said...

Hi Andrejus,

I got it working generically.
My next concern is I don’t see the active sessions or datasource connections going down when i close the browser, but it is using the correct logout url and getting the correct code back.

Is it not supposed to remove active sessions and datasource connections from the resources?

Thank you

Andrejus Baranovskis said...

Active sessions and DB connections should be removed. If you are doing logout through ADF Authentication servlet, resources should be cleaned.

You can add some debug code into ADF BC to see if AM is destroyed.

Regards,
Andrejus

Anonymous said...

Good Day Andrejus,
Is there a way to have that sample working with jdeveloper 12.1.3 (JDK 1.7).
Thank you
Emile Bitar

Andrejus Baranovskis said...

Hi,

WebSocket logic works on 12.1.3, you can test it yourself.

Regards,
Andrejus

Anonymous said...

Again,it does nwork under jdev 12.1.3
try testing yourself.
Regards,
Emile

Andrejus Baranovskis said...

It works in 12.1.3 and 12.2.1. Please be more specific, what doesnt work in your case.

Andrejus

Andrejus Baranovskis said...

Indeed, to make it work on 12.1.3 - remove this method from WebSocket service class: processMessage. We are not sending any messages, and encoder/decoder is not needed.

Regards,
Andrejus

Rob Lefebvre said...

Hello Andrejus,

Thanks a lot for this useful article, I was able to setup our application (12.1.3) in combination with WebSockets thanks to this article.
But I still have two open questions that I would like to post here to listen to your opinions on this matter:

1) I managed to configure our application in combination with WebSockets, however I noticed a difference in the way JDev 12.2.1 works (with a configurator class) and the way 12.1.3 works (with just @onOpen and @onClose).
I did not manage to get the communication correct between our application in 12.1.3 and the WebSockets, using a configurator.
Did I do something wrong and should the configuration class work in 12.1.3 as well, or is normal that the configurator class will not work for versions earlier than 12.2.1?

2) The sample application posted here will work correctly, provided that you stay on the same af:document tag.
But from as soon as the end user navigates to another page (not another fragment but really another af:document page), the WebSockets close operation will be detected. Technically I understand why this happens and it seems very logically to me (since the connection is made in JavaScript and the current page is exited), but how can you make the distinction between a user that is navigating normally through your application, and the end users that have exited the application using the browser X-close icon?
Because in a real (ADF) application you do not want to close an open session if a user navigates correctly from one part of your application to another. But you do want to release the open session when the browser is closed.
How would you handle that issue?

Thanks a lot for your answer(s).

Kind regards,
Rob Lefebvre

Andrejus Baranovskis said...

Hi Rob,

1. The same setup, as posted in the sample, works fine in our 12.1.3 project. Just in case, see comment above - make sure to remove processMessage in WebSocket service class, to run on 12.1.3

2. Yes, WebSocket will close when you navigate to different page. In practice, most of the modern ADF apps are implemented as single page apps. User always stays in the same page and only fragments are changed. Otherwise, if you want to handle page navigation, you would need to send some sort of event, to notify WebSocket server side, this is not browser close

Regards,
Andrejus

Rob Lefebvre said...

Hello Andrejus,

Thanks a lot for your reply. We do make use of page fragment navigation inside a general page, but we have different logical area's of our pretty big application. And those different area's are based on different template's and will have different URL's.
So as long as a user remains in the same area this was not a problem, but when he/she switches from one area to another we do not want yo close his/her session.

But your answer was exactly what I wanted to know; so now we can continue our development thanks to your advice.

Thanks a lot!

Andrejus Baranovskis said...

Very good. Good luck !

Andrejus

Anonymous said...

Hi Andrejus,

Thanks for the great post.

I can make it work with 12.1.3.

But I got problem when user refresh the page, or when he navigates to other page and then go back.

So do you have any idea to fix that?

Thanks in advance for your helps :)

Andrejus Baranovskis said...

Check my comments above on this topic.

Andrejus

Unknown said...

Hi Andrejus


I'm new to ADF , m trying to run the sample application you provided,its throwing connection time out error. Is there any configuration i have to do before running the app. kindly help.


Thanks and regards
Vinay





Andrejus Baranovskis said...

You need to make sure it connects to your local DB.

Andrejus