Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Changing data on GET page request (dealing with preloading requests)

I have a portlet. When the portlet loads, then before the first view is rendered, in some cases there is a need to call a repository which changes data in the database. I wouldn't go into more detail about why this is necessary and answers about this being a design flaw are not helpful. I am aware that it is a design flaw but I would still like to find out an alternative solution to the following problem:

The problem with this set-up is, that browsers send preloading requests. For example the URL of the page where the portlet resides is /test-portlet. Now when you type it in your address-bar then if you have it in your browser history, then the browser sends a GET request to the page already when it suggests it to you. If you press enter before the first GET request is resolved, then the browser sends a new GET request. This means that the portlet receives 2 separate requests which it starts to process parallelly. The first database procedure might work correctly but considering the nature of the database procedure, the second call usually gives an exception.

What would be a nice clean way to deal with the aforementioned problem from the Java application?

Sidenote: I am using Spring MVC.

A simple example of a possible controller:

@RequestMapping
public String index( Model model, RenderRequest request ){
    String username = dummyRepository.changeSomeData(request.getAttribute("userId"));
    model.add("userName", username);
    return "view";
}

I would be interested in a solution to block the first execution altogether. For example somekind of a redirect to POST from controller which the browser wouldn't trigger. Not sure if it is achievable though.

like image 809
Reins Avatar asked May 22 '15 11:05

Reins


Video Answer


3 Answers

Using locks I think you could solve it, making the secound request wait for the first to finish and then processing it. I don't have experience with locks in java but i found another stack exchange post about file locks in jave: How can I lock a file using java (if possible)

like image 63
Myrtue Avatar answered Oct 14 '22 19:10

Myrtue


Please refer to this answer, it might help you to detect and ignore some preloading requests. However you should also make sure the 'worst case' works, perhaps using the locking as suggested by @jpeg, but it could be as easy as using a synchronize block somewhere.

like image 44
geert3 Avatar answered Oct 14 '22 18:10

geert3


Since I don't see that chrome adds some specific header (or anyhow notifies the server about prerendering state) it is probably not possible to detect it on the server side... at least not directly. You can however simulate the detection on client side and later combine it with server call.

Notice that you can detect prerendering on the client side:

if (document.webkitVisibilityState == 'prerender' ||  document.visibilityState ==    'prerender' || document.visibilityState[0] == 'prerender') {
    // prerendering takes place
}  

Now, you can break preloading on client side by showing alert box in case browser is in preloading state (or you can probably do the same with just some error in javascript, instead of using alert()):

 if (document.webkitVisibilityState == 'prerender' ||  document.visibilityState ==    'prerender' || document.visibilityState[0] == 'prerender') {
    alert('this is alert during prerendering..')
} 

Now when chrome prerenders the page it will fail because the javascript alert will prevent the browser to continue executing javascript.

If you type in chrome: chrome://net-internals/#prerender you can track when and for which pages chrome executes prerendering. In case of above example (with alert box during prerendering) you can see there:

Link Rel Prerender (cross domain) http://some.url.which.is.preloaded Javascript Alert 2015-06-07 19:26:18.758

The final state - Javascript Alret proves that chrome failed to preload the page (I have tested this).

Now how can this solve your issue? Well, you can combine this with asynchronous call (AJAX) and load some content (from another url) depending on wheater the page is actually prerendering or not.

Consider following code (which might be rendered by your portlet under url /test-portlet):

<html>
  <body>


    <div id="content"></div>

<script>

if (document.webkitVisibilityState == 'prerender' ||  document.visibilityState ==    'prerender' || document.visibilityState[0] == 'prerender') {
    // when chrome uses prerendering we block the request with alert
    alert('this is alert during prerendering..');
} else {
    // in case no prerendering takes place we load the actual content asynchronously

    var xhr = new XMLHttpRequest();
    xhr.onreadystatechange = function() {
        if (xhr.readyState == 4) {
             // when the content is loaded we place the html inside "content" div               
             document.getElementById('content').innerHTML = xhr.responseText; 

        }
    }
    xhr.open('GET', '/hidden-portlet', true); // we call the actual portlet
    xhr.send(null);

}

</script>

  </body>
</html>  

As you see the /hidden-portlet is only loaded in case browser is loading the page normally (without preloading). The server side handler under url /hidden-portlet (which can be another portlet/servlet) contains actual code which should not be executed during prerendering. So it is the /hidden-portlet which executes

dummyRepository.changeSomeData(request.getAttribute("userId"));

This portlet can also return normal view (rendered html) which will be asynchronously placed on the page under url /test-portlet thanks to the trick on /test-portlet: document.getElementById('content').innerHTML = xhr.responseText;.

So to sumarize the portlet under address /test-portlet only returns html with a javascript code which triggers actual portlet.

If you have many fragile portlets, you can go with this even further, so you can parametrize you /test-portlet with request parameter like /test-portlet?actualUrl=hidden-portlet so that address of the actual portlet is taken from url (which can be read as request parameter on server side). Server will in this case dynamically render the url which should be loaded:

So instead of hardcoded:

xhr.open('GET', '/hidden-portlet', true); 

you will have

xhr.open('GET', '/THIS_IS_DYNAMICALLY_REPLACED_EITHER_ON_SERVER_OR_CLIENT_SIDE_WITH_THE_ADDRES_FROM_URL', true); 
like image 40
walkeros Avatar answered Oct 14 '22 18:10

walkeros