Handling a Long Running jsp request on the server using Ajax and threads - ajax

I am trying to implement a solution for a long running process on the server where it is taking about 10 min to process a pdf generation request. The browser bored/timesout at the 5 mins. I was thinking to deal with this using a Ajax and threads. I am using regular javascript for ajax. But I am stuck with it.
I have reached till the point where it sends the request to the servlet and the servlet starts the thread.Please see the below code
public class HelloServlet extends javax.servlet.http.HttpServlet
implements javax.servlet.Servlet {
protected void doGet(HttpServletRequest request, HttpServletResponse response)
throws ServletException, IOException {
}
protected void doPost(HttpServletRequest request, HttpServletResponse response)
throws ServletException, IOException {
System.out.println("POST request!!");
LongProcess longProcess = new LongProcess();
longProcess.setDaemon(true);
longProcess.start();
request.getSession().setAttribute("longProcess", longProcess);
request.getRequestDispatcher("index.jsp").forward(request, response);
}
}
class LongProcess extends Thread {
public void run() {
System.out.println("Thread Started!!");
while (progress < 10) {
try { sleep(2000); } catch (InterruptedException ignore) {}
progress++;
}
}
}
Here is my AJax call
<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd">
<html><head>
<meta http-equiv="Content-Type" content="text/html; charset=ISO-8859-1">
<title>My Title</title>
<script language="JavaScript" >
function getXMLObject() //XML OBJECT
{
var xmlHttp = false;
xmlHttp = new XMLHttpRequest(); //For Mozilla, Opera Browsers
return xmlHttp; // Mandatory Statement returning the ajax object created
}
var xmlhttp = new getXMLObject(); //xmlhttp holds the ajax object
function ajaxFunction() {
xmlhttp.open("GET","HelloServlet" ,true);
xmlhttp.onreadystatechange = handleServerResponse;
xmlhttp.setRequestHeader('Content-Type', 'application/x-www-form-urlencoded');
xmlhttp.send(null);
}
function handleServerResponse() {
if (xmlhttp.readyState == 4) {
if(xmlhttp.status == 200) {
document.forms[0].myDiv.value = xmlhttp.responseText;
setTimeout(ajaxFunction(), 2000);
}
else {
alert("Error during AJAX call. Please try again");
}
}
}
function openPDF() {
document.forms[0].method = "POST";
document.forms[0].action = "HelloServlet";
document.forms[0].submit();
}
function stopAjax(){
clearInterval(intervalID);
}
</script>
</head>
<body><form name="myForm">
<table><tr><td>
<INPUT TYPE="BUTTON" NAME="Download" VALUE="Download Queue ( PDF )" onclick="openPDF();">
</td></tr>
<tr><td>
Current status: <div id="myDiv"></div>%
</td></tr></table>
</form></body></html>
But I dont know how to proceed further like how will the thread communicate the browser that the process has complete and how should the ajax call me made and check the status of the request.
Please let me know if I am missing some pieces. Any suggestion if helpful.

Jetty 9.0 Distribution includes a long polling chat example, which includes an async servlet and javascript client that work in tandem. A poll request is initiated by the client which causes the servlet to start an async loop that deliberately times out at a chosen interval. Some browsers last a long time, however some only last 30 seconds. So, setting the timeout to less than 30 seconds is recommended. When the servlet times out, it sends a signal to the client causing the client to intiate another poll. Data can be sent at any time through the response, and the client can simply connect again thereafter if necessary. This has the effect of establishing an open channel from a server to a client.
// This starts an async request
AsyncContext asyncCtx = request.startAsync();
asyncCtx.setTimeout(10000); // 10 seconds
asyncCtx.addListener(member);
// This is the timeout handler which tells the client to continue to poll
#Override
public void onTimeout(AsyncEvent event) throws IOException {
System.out.println("Client onTimeout\r\n");
AsyncContext asyncCtx = asyncCtxAtomRef.get();
if ((asyncCtx != null) && asyncCtxAtomRef.compareAndSet(asyncCtx, null))
{
HttpServletResponse response = (HttpServletResponse)asyncCtx.getResponse();
response.setContentType("text/json;charset=utf-8");
PrintWriter out=response.getWriter();
out.print("{action:\"poll\"}");
asyncCtx.complete();
}
}
Essentially, any response sent to the client with an action of "poll", has the effect of causing the client to automatically reconnect. It seems to work really well, so you might want to check it out.

You can either use Servlet 3.0 asynchronous processing or existing libraries like atmosphere (using servlet 3.0 underneath).
The idea is to call servlet and start AsyncContext. You then pass that context to your thread and use it to periodically send some progress. On the client side reading this stream is a bit tricky, see: jquery ajax, read the stream incrementally? Do not access original HttpServletResponse inside a thread, it won't work.
Here is a sample code:
#WebServlet("/hello" asyncSupported=true)
public class HelloServlet extends HttpServlet {
public void doGet(HttpServletRequest req, HttpServletResponse res) {
AsyncContext async = request.startAsync(req, res);
LongProcess longProcess = new LongProcess();
longProcess.setDaemon(true);
longProcess.start();
}
}
class LongProcess extends Thread {
private final AsyncContext asyncContext;
public LongProcess(AsyncContext asyncContext) {
this.asyncContext = asyncContext;
}
public void run() {
System.out.println("Thread Started!!");
while (progress < 10) {
try { sleep(2000); } catch (InterruptedException ignore) {}
progress++;
//use asyncContext here to send progress to the client incrementally
}
}
}
See also Asynchronous Support in Servlet 3.0.
You can also use atmosphere library which will do it for you and works quite well.

Related

Content-Security-Policy nonce mismatch on error pages (e.g. 404)

We use a Content-Security-Policy nonce successfully on normal pages (Thymeleaf templates). But when we try to do the same on error pages (e.g. 404), the nonce received as a model attribute does not match the nonce specified in the Content-Security-Policy HTTP header. This mismatch causes a policy violation and therefore script errors in our custom error page (also generated from a Thymeleaf template). In the Chrome console, the errors are
Content Security Policy: The page’s settings blocked the loading of a resource at http://localhost:8080/webjars/jquery/3.6.0/jquery.min.js (“script-src”).
Content Security Policy: The page’s settings blocked the loading of a resource at inline (“script-src”).
We enable the policy in the Spring Security configuration:
#Bean
public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
return
http.headers()
.contentSecurityPolicy("script-src 'strict-dynamic' 'nonce-{nonce}'")
.and().and()
.addFilterBefore(new ContentSecurityPolicyNonceFilter(), HeaderWriterFilter.class)
.build();
}
The filter is:
public class ContentSecurityPolicyNonceFilter extends GenericFilterBean {
#Override
public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException {
var nonceArray = new byte[32];
(new SecureRandom()).nextBytes(nonceArray);
var nonce = Base64Utils.encodeToString(nonceArray);
request.setAttribute("cspNonce", nonce);
chain.doFilter(request, new NonceResponseWrapper((HttpServletResponse) response, nonce));
}
}
NonceResponseWrapper is
class NonceResponseWrapper extends HttpServletResponseWrapper {
private final String nonce;
NonceResponseWrapper(HttpServletResponse response, String nonce) {
super(response);
this.nonce = nonce;
}
private String getHeaderValue(String name, String value) {
final String retVal;
if (name.equals("Content-Security-Policy") && StringUtils.hasText(value)) {
retVal = value.replace("{nonce}", nonce);
} else {
retVal = value;
}
return retVal;
}
#Override
public void setHeader(String name, String value) {
super.setHeader(name, getHeaderValue(name, value));
}
#Override
public void addHeader(String name, String value) {
super.addHeader(name, getHeaderValue(name, value));
}
}
The nonce value is provided to the page via ControllerAdvice:
#ControllerAdvice
public class ContentSecurityPolicyControllerAdvice {
#ModelAttribute
public void addAttributes(Model model, HttpServletRequest request) {
model.addAttribute("nonce", request.getAttribute("cspNonce"));
}
}
The working index page and the dysfunctional error page follow the same pattern in Thymeleaf and HTML terms:
index.html:
<html xmlns="http://www.w3.org/1999/xhtml" xmlns:th="http://www.thymeleaf.org">
<head>
<title>Application</title>
<script th:src="#{/webjars/jquery/3.6.0/jquery.min.js}" th:nonce="${nonce}"></script>
<script th:inline="javascript" th:nonce="${nonce}">
const randomNumber = /*[[${randomNumber}]]*/ -1;
$(function() {
$('#a-number').text(randomNumber);
});
</script>
</head>
<body>
<h1>Welcome</h1>
<p>Your random number is <span id="a-number">unknown</span>.</p>
</body>
</html>
error/404.html:
<html xmlns="http://www.w3.org/1999/xhtml" xmlns:th="http://www.thymeleaf.org">
<head>
<title>404 Error</title>
<script th:src="#{/webjars/jquery/3.6.0/jquery.min.js}" th:nonce="${nonce}"></script>
<script th:nonce="${nonce}">
$(function() {
const timestampString = new Date().toISOString();
$('#timestamp').text(timestampString);
});
</script>
</head>
<body>
<h1>404 - Page Not Found</h1>
<p>The current time is <span id="timestamp">unknown</span>.</p>
</body>
</html>
Application debug output when loading the invalid URL shows
Nonce for request = qPhdJiUAAkKHrwQBvxzxUz0OUUU4UXaxLcDErhl4g7U=
Content-Security-Policy = script-src 'strict-dynamic' 'nonce-qPhdJiUAAkKHrwQBvxzxUz0OUUU4UXaxLcDErhl4g7U='
Nonce for request = OiZmhtGlYMgb4X+pcFIwM41GzEkre3YvfkLCHFqoqIU=
Nonce for view model = OiZmhtGlYMgb4X+pcFIwM41GzEkre3YvfkLCHFqoqIU=
Nonce for request = sCbXWXA0TPjw+I/dui2bmee1vKKXG1Y2Xt3G7JkuZ04=
Content-Security-Policy = script-src 'strict-dynamic' 'nonce-sCbXWXA0TPjw+I/dui2bmee1vKKXG1Y2Xt3G7JkuZ04='
Nonce for request = hsGwh4+5oqg0W51zNprrT41rHnEeJRdHHO8KTMCSwL8=
Content-Security-Policy = script-src 'strict-dynamic' 'nonce-hsGwh4+5oqg0W51zNprrT41rHnEeJRdHHO8KTMCSwL8='
In this run, the nonce interpolated into the policy is qPhdJiUAAkKHrwQBvxzxUz0OUUU4UXaxLcDErhl4g7U=, while the page is getting OiZmhtGlYMgb4X+pcFIwM41GzEkre3YvfkLCHFqoqIU= from the view model.
I've constructed a minimal, runnable (./gradlew bootRun) code base for this problem at https://gitlab.com/russell-medisens/nonce-problem.git for anyone who might take a look.
I believe I've solved this problem by changing the filter to avoid overwriting an existing nonce:
public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException {
final String nonce;
final Object existingNonce = request.getAttribute(REQUEST_NONCE_ATTRIBUTE);
if (existingNonce == null) {
final var nonceArray = new byte[NONCE_SIZE];
SECURE_RANDOM.nextBytes(nonceArray);
nonce = Base64Utils.encodeToString(nonceArray);
request.setAttribute(REQUEST_NONCE_ATTRIBUTE, nonce);
System.err.format("Nonce generated in filter = %s%n", nonce);
} else {
nonce = (String) existingNonce;
System.err.format("Existing nonce retained in filter = %s%n", nonce);
}
chain.doFilter(request, new NonceResponseWrapper((HttpServletResponse) response, nonce));
}
My understanding is that when a requested page is not found, Spring performs a forward (rather than a redirect), but the filter is invoked a second time in the process of serving the substituted 404 page. This code change preserves any existing nonce so that it can be provided to the view model for the error page.

jersey at serverside nerver stopped processing a request even though client cancelled the connection

Am using restyGwt to send a request to jersey , I have closed the request using request object at client side(request.cancel) , but i found that the server is processing my request even though i closed at client side , it is never notified to server that the connection is stopped.
I want a way to tell the server as soon as the client cancel a request , to stop proccessing that request
this is my client resty gwt code
#POST
#Produces("application/json")
#Consumes("application/json")
#Path("servicepoint/getAllServicepointAndCount")
public Request findAllServicepointandCount(#QueryParam("startIndex") Integer startIndex,
#QueryParam("maxSize") Integer maxSize,
MethodCallback<ServicepointResponse> methodCallback);
this is the gwt class where am making my request to server
ServicepointRestyService service = GWT
.create(ServicepointRestyService.class);
((RestServiceProxy) service).setResource(resource);
final Request method=service.findAllServicepointandCount( index, length,
new MethodCallback<ServicepointResponse>() {
#Override
public void onFailure(Method method, Throwable exception) {
Window.alert(exception.getMessage());
}
#Override
public void onSuccess(Method method,
ServicepointResponse response) {
Window.alert("response.getMassege");
}
});
Timer t=new Timer() {
#Override
public void run() {
Window.alert("cancelled");
method.cancel();// cancel connection
}
};
t.schedule(2000);
}
jersey impl
#POST
#Produces("application/json")
#Consumes("application/json")
#Path("/getAllServicepointAndCount")
public ServicepointResponse findAllServicepointandCount(
#QueryParam("startIndex") Integer startIndex,
#QueryParam("maxSize") Integer maxSize) {
logger.finer("Entering startIndex" + startIndex + "" + maxSize);
ServicepointResponse data = new ServicepointResponse();
List<ServicepointPojo> spPojos = new ArrayList<>();
try {
while(true){
System.out.println(new Date().getTime());
Thread.sleep(1000);
}catch (Exception e) {
e.printStackTrace();
}
eventhough i stopped my request after 2sec, server still prints the date and time it never stoped , i have written this code to check this functionality

JSR 286 Portlet Count the number of user online

I want to create a JSR 286 Portlet named VisitCounter to install on websphere Portal 8.0. VisitCounter counts the number of user online.
public void doView(RenderRequest request, RenderResponse response) throws PortletException, IOException {
// Set the MIME type for the render response
response.setContentType(request.getResponseContentType());
// Check if portlet session exists
VisitCounterPortletSessionBean sessionBean = getSessionBean(request);
if( sessionBean==null ) {
//response.getWriter().println("<b>NO PORTLET SESSION YET</b>");
return;
}
........
}
How to make this?
Thanks!
public void doView(RenderRequest request, RenderResponse response) throws PortletException, IOException {
// Set the MIME type for the render response
response.setContentType(request.getResponseContentType());
// Check if portlet session exists
VisitCounterPortletSessionBean sessionBean = getSessionBean(request);
if( sessionBean==null ) {
//response.getWriter().println("<b>NO PORTLET SESSION YET</b>");
return;
}
........
}
Using application scope...

Struts2 and servlet integration

i am getting data from action class to servlet by adding data to session.whenever i am clicking the item in select list onchange event is fired that function is invoked the our servlet up to now OK,whenever we send second time request that servlet is not called why? and also comparsion is failed it will maintain previous values only.here i am sending request from ajax.pls can any one provide solution ?
AjaX code
function verify_details()
{
var resourceId=document.getElementById("res").value
var url="/EIS10/ResourceTest?resourceId="+resourceId;
ajax(url);
}
Action class Code:
listResource=taskService.getUserList(taskId);
System.out.println("The list Of Resources are::"+listResource);
HttpSession session=request.getSession();
session.setAttribute("listResource", listResource);
ServletCode
protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException
{
System.out.println("Servlet is Called...........");
String resourceId=request.getParameter("resourceId");
boolean t=false;
System.out.println("Your Clicked Id::"+resourceId);
HttpSession session=request.getSession();
List l=(List)session.getAttribute("listResource");
System.out.println("Resource List in Servlet:"+l);
if(l!=null)
{
System.out.println("The Size of List::"+l.size());
Iterator itr=l.iterator();
while(itr.hasNext())
{
String s=itr.next().toString();
System.out.println("Elements in List:"+s);
if(s.equals(resourceId))
t=true;
}
response.setContentType("text/html");
if (t) {
response.getWriter().write("Y");
} else {
response.getWriter().write("N");
}
}
}
}
It's probably because the browser returns the contents from its cache at the second request. See http://spacebug.com/solving_browser_caching_problem_of_ajax-html/ for a solution, or use an AJAX library (jQuery for example) which can handle this for you.
Besides, if you're using Struts, why do you use a bare servlet to handle your AJAX call? Why don't you use a Struts action?

Session management in gwt

I am using GWT for my client side application. However, I am not sure how I can handle session management. The GWT application resides on one page, all server calls are done via AJAX. If a session expires on the server. let's assume the user didn't close the browser, and sending some request to server using RPC, how could my server notify the application that the session has expired and that the client side portion should show the login screen again?My sample code :
ContactDataServiceAsync contactDataService = GWT
.create(ContactDataService.class);
((ServiceDefTarget) contactDataService).setServiceEntryPoint(GWT
.getModuleBaseURL()
+ "contactDatas");
contactDataService.getContact(2,
new AsyncCallback<ContactData>() {
public void onFailure(Throwable caught) {
//code to show error if problem in connection or redirect to login page
}
public void onSuccess(ContactData result) {
displayContact(result);
}
});
If session expires only it has to show login screen, otherwise it wants to show some error using Window.alert().
How to do this and what are all the codes needed in server side and client side?
You could have the server throw an AuthenticationException to the client in case the user has been logged out.
This will be catched in the callbacks onFailure method, which then can redirect the user to the login-page.
Edit:
AuthenticationException is not a standard exception of course, i was just making an example. It might be best to stick with the standard exceptions.
To try if you caught an specific exception you could use the instanceof operator
public void onFailure(Throwable e) {
if(e instanceof AuthenticationException) {
redirecttoLogin();
}
else {
showError(),
}
}
This does not directly apply to those using RPC, but for those of you who are not using RPC, you should send a HTTP 401 from the server. Then you can check that status code in your RequestBuilder callback.
Client: All Callbacks extend a Abstract Callback where you implement the onFailur()
public abstract class AbstrCallback<T> implements AsyncCallback<T> {
#Override
public void onFailure(Throwable caught) {
//SessionData Expired Redirect
if (caught.getMessage().equals("500 " + YourConfig.ERROR_MESSAGE_NOT_LOGGED_IN)) {
Window.Location.assign(ConfigStatic.LOGIN_PAGE);
}
// else{}: Other Error, if you want you could log it on the client
}
}
Server: All your ServiceImplementations extend AbstractServicesImpl where you have access to your SessionData. Override onBeforeRequestDeserialized(String serializedRequest) and check the SessionData there. If the SessionData has expire then write a spacific error message to the client. This error message is getting checkt in your AbstrCallback and redirect to the Login Page.
public abstract class AbstractServicesImpl extends RemoteServiceServlet {
protected ServerSessionData sessionData;
#Override
protected void onBeforeRequestDeserialized(String serializedRequest) {
sessionData = getYourSessionDataHere()
if (this.sessionData == null){
// Write error to the client, just copy paste
this.getThreadLocalResponse().reset();
ServletContext servletContext = this.getServletContext();
HttpServletResponse response = this.getThreadLocalResponse();
try {
response.setContentType("text/plain");
response.setStatus(HttpServletResponse.SC_INTERNAL_SERVER_ERROR);
try {
response.getOutputStream().write(
ConfigStatic.ERROR_MESSAGE_NOT_LOGGED_IN.getBytes("UTF-8"));
response.flushBuffer();
} catch (IllegalStateException e) {
// Handle the (unexpected) case where getWriter() was previously used
response.getWriter().write(YourConfig.ERROR_MESSAGE_NOT_LOGGED_IN);
response.flushBuffer();
}
} catch (IOException ex) {
servletContext.log(
"respondWithUnexpectedFailure failed while sending the previous failure to the client",
ex);
}
//Throw Exception to stop the execution of the Servlet
throw new NullPointerException();
}
}
}
In Addition you can also Override doUnexpectedFailure(Throwable t) to avoid logging the thrown NullPointerException.
#Override
protected void doUnexpectedFailure(Throwable t) {
if (this.sessionData != null) {
super.doUnexpectedFailure(t);
}
}

Resources