14

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.

Reins
  • 1,109
  • 1
  • 17
  • 35
  • have you examined the request headers? are there any headers that indicate that you are in the "preloading" scenario? In which case you could skip whatever you need to skip. – geert3 Jun 01 '15 at 07:55
  • can u breifly describe what's ur database procedures are?? – MS Ibrahim Jun 01 '15 at 10:09
  • @MSIbrahim I have just simplified the scenario. Actually there is a REST API call which manipulates 2 databases. I don't think this is relevant because I certainly don't want to make changes on a lower level as it is strictly the web-layer's problem. – Reins Jun 02 '15 at 05:32
  • what sequence u need to run your project u have any idea like get/redirect/post something like that – MS Ibrahim Jun 02 '15 at 06:22
  • @MSIbrahim a bit unclear what exactly is your question. As stated in my initial question, Ihave though about a possible get/redirect/post (_"For example somekind of a redirect to POST from controller which the browser wouldn't trigger. Not sure if it is achievable though."_), but I am not sure how to implement it in this scenario or even if it would be possible or valid. – Reins Jun 04 '15 at 13:22

3 Answers3

1

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)

Community
  • 1
  • 1
Myrtue
  • 306
  • 2
  • 14
  • A possible idea, but the second request would then still be processed. I would be more interested in a solution to block both executions altogether. For example somekind of a redirect from controller which the browser wouldn't trigger on a preloading request. Not sure if it is achievable. Thanks though. – Reins May 22 '15 at 11:49
  • That is a matter off http protocol, which I don't think is possible. Browsers have made a lot of "scetcy" optimizations that makes them do a lot of requests that are both hard for the server they request, and not to smart in data amount needed to be transfered. The only way to make browsers like chrome work with your website, is to make sure it can handle it. But give http protocol with connection type a look, if you can do what you ask that is the way you can do it. – Myrtue May 23 '15 at 09:17
1

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.

Community
  • 1
  • 1
geert3
  • 7,086
  • 1
  • 33
  • 49
  • Unfortunately there are cases in which the requests are identical. I think the problem might mainly be Chrome, as stated here: [link](http://stackoverflow.com/a/29097039/1826152). _it might help you to detect and ignore some preloading requests._ - I would find it nice, if there would be a single solution to avoid having a bunch of different checks like `if(thisheader); if(thatheader);` etc, and which would still not be able to cover all the possibilities. – Reins Jun 01 '15 at 08:44
  • 1
    One soultion might be JS Visibility API but I would still wait for a possible solution to the problem from the back-end side. I find it a bit hackish to deal with this from the client/browser side. – Reins Jun 01 '15 at 08:45
1

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); 
walkeros
  • 4,736
  • 4
  • 35
  • 47
  • Thanks for your input. As I've stated in another answer's comments, I know it is possible to deal with from the client side. As it seems this might be the way to go as a bountied question has not found any definite answers in a week (I'd still say it is hackish). I am really sorry but from this question's perspective I cannot accept the answer, as the question is expecting a solution from the server side - possible or not. You have been very thorough (although asynchronous portlet loading was never a problem :) ) but an upvote is the most i can do. – Reins Jun 08 '15 at 07:42
  • As you wish:) At the top I only stated that as far as I know you can not solve it using server side only, because the magic is on client. It is client which needs to notify the server about "preloading phase". Since chrome does not do this you need to send notification yourself. Alternatively you can think of solution which will block the second request (the real request), but you can not not block the first one. – walkeros Jun 08 '15 at 07:47
  • The problem with blocking the second request is that the second request is actually the one that constructs the view that is returned to the user. And it uses the data from the repository. One theoretical solution from the server side would be to add everything what is needed to the session during the first requests execution and then check the session during the second request. Although this also isn't error-proof in any way, because the requests might come in within a millisecond time-frame and by the time the second request executes there still might be nothing in the session. – Reins Jun 08 '15 at 08:15
  • Using session seems like an option, but there is also another problem with this solution. Preloading request can be executed causing the data to be placed in session, but user might not hit enter, so he will never go to this page. This way (having many users) you can spam your memory with unneeded session data. Another thing is that you still can not distinguish on the server side wheather it is user who hit the page twice (he can type twice the same in url, although it seems rare) or it was 1 request for preloading and another real request. – walkeros Jun 08 '15 at 08:51