Article updated on

Simple Proxy Servlet Example for Slow Requests to Prevent Timeout

In java if you are dealing with slow requests to a server but you need a fast response but you can't modify the code that produces that response, one solution is to create a proxy that will call that final URL for you and tell you when it's finished.

Here you have one Servlet example that does that. Mind the following features:

  • It needs to be in the same application.
  • It allows GET and POST data.
  • It keeps cookie headers (tomcat session is not lost).
  • Additional headers will be lost.
  • It will only accept one request per session.
  • It will only spawn a threat per request.
  • If more than one different requests are sent before the final call has finished it will response the most recent one.
  • If the user goes to a different page while the proxy hasn't finished the request, the response will be kept in session.
  • Once the final URL request is finished it can be called again.
  • It will send and extra parameter to the final URL.

In this example a JSP called slow.jsp will be called. This JSP is meant to last 15 seconds to complete. If called with the SimpleProxyServlet a processing.jsp page will be shown every 3 seconds until the slow.jsp is finished.

Example working

img/0/99/uno.png

img/0/99/dos.png

img/0/99/tres.png

Download war file with sources here

SimpleProxyServlet.java

package com.test;
import java.io.BufferedReader;
import java.io.DataOutputStream;
import java.io.IOException;
import java.io.InputStreamReader;
import java.io.PrintWriter;
import java.io.StringWriter;
import java.net.MalformedURLException;
import java.net.URL;
import java.util.Enumeration;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.net.HttpURLConnection;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import javax.servlet.http.HttpSession;
public class SimpleProxyServlet extends HttpServlet {
    private static final long serialVersionUID = 2110292828406248147L;
    //this servlet proxy should always receive this parameter
    public  static final String PARAM_PROXY= "proxy_final_url";
    //will wait the first time
    private static final long WAIT_THE_FIRST_TIME = 500l;
    //process that will keep calling itself until the final url is loaded
    private static final String JSP_PROCESS = "/processing.jsp";
    public void doGet(HttpServletRequest request, HttpServletResponse response)
            throws ServletException, IOException {       
        processRequest(request, response, null);
    }
    public void doPost(HttpServletRequest request, HttpServletResponse response)
            throws ServletException, IOException {
        @SuppressWarnings("unchecked")
        //this will get all post parameter data
        Enumeration<String> parameterNames = request.getParameterNames();
        StringBuilder sb = new StringBuilder();
        while (parameterNames.hasMoreElements()) {
            String paramName = parameterNames.nextElement();
            sb.append(paramName + "=");
            sb.append(request.getParameter(paramName) );
            if(parameterNames.hasMoreElements()){
                sb.append("&");
            }
        }
        processRequest(request, response, sb.toString());
    }
    private void processRequest(HttpServletRequest request, HttpServletResponse response, String postData)
            throws ServletException, IOException {
        PrintWriter out = response.getWriter();
        String urlParam = (String)request.getParameter(PARAM_PROXY);
        HttpSession session = request.getSession(true);       
        AsynchornousReq prevAsReq = null;
        if(session.getAttribute("userId")!=null ){
            prevAsReq = (AsynchornousReq) session.getAttribute("userId");
        }
        response.setContentType("text/html");
        // this will create the final URL inside your proyect
        String final_url = request.getScheme() + "://" +  
                request.getServerName()  + ":" +
                request.getServerPort()  +
                request.getContextPath() + "/" + urlParam ;
        // there is previous request, then it should be the first
        if(prevAsReq==null){
            // if the final URL parameter is empty it should return error
            if(urlParam==null){
                response.setStatus(HttpServletResponse.SC_BAD_REQUEST);
                out.print("Error " + PARAM_PROXY + " = null");
                return;            
            }
            // creates an Asynchronous URL load request
            AsynchornousReq asReq = new AsynchornousReq(final_url, request.getQueryString(), postData);
            // the first time waits in case the response is not long enough
            // fell free to remove this..
            try {
                Thread.sleep(WAIT_THE_FIRST_TIME);
            } catch (InterruptedException e) { e.printStackTrace(); }
            if(asReq.isFinished()){
                out.print(asReq.getResponse());                
                response.setStatus(asReq.getResponseCode());
                return;
            // if hasn't finished therefore is an slow request
            } else {
                session.setAttribute("userId", asReq);
            }
        // this request is already in session
        } else {           
            if(prevAsReq.isFinished()){
                session.setAttribute("userId", null);
                out.print(prevAsReq.getResponse());
                response.setStatus(prevAsReq.getResponseCode());
                return;
            // in case another request different from the original has been requested
            } else if(urlParam!=null && !prevAsReq.getUrl().equals(final_url)) {
                AsynchornousReq as = new AsynchornousReq(final_url, request.getQueryString(), postData);
                session.setAttribute("userId", as);
            }
        }
        request.getRequestDispatcher(JSP_PROCESS).forward(request,response);
    }       
    /**
     * This class will create an asynchronous call to an URL
     * It will get the response code from the final URL
     * boolean finished will be set to true when done
     * @author jcgonzalez.com
     */
    class AsynchornousReq {
        AsynchornousReq(String final_url, String urlGetParams, String postData){
            this.url = final_url;
            this.urlGetParams = urlGetParams;
            this.postData = postData;
            loadURL();
        }
        private String url;
        private boolean finished = false;
        private Integer responseCode = HttpServletResponse.SC_OK;
        private String response = null;
        private String urlGetParams = null;
        private String postData = null;
        private void loadURL(){
            ExecutorService executorService = Executors.newSingleThreadExecutor();       
            executorService.execute(new Runnable() {
                public void run() {
                    response= getResponse(url);
                    finished = true;
                }
            });
            executorService.shutdown();
        }
        private String getResponse(String url) {
            StringBuilder response = new StringBuilder();
            try {                
                if(urlGetParams!=null){
                    url=url+"?"+urlGetParams;
                }
                URL obj = new URL(url);
                HttpURLConnection con = (HttpURLConnection) obj.openConnection();
                // Send post request
                con.setDoOutput(true);
                if(postData!=null){
                    DataOutputStream wr = new DataOutputStream(con.getOutputStream());
                    wr.writeBytes(postData);
                    wr.flush();
                    wr.close();
                }
                responseCode = con.getResponseCode();                                
                BufferedReader in = new BufferedReader(
                                        new InputStreamReader(
                                            con.getInputStream()));
                String inputLine;
                while ((inputLine = in.readLine()) != null)
                    response.append(inputLine);
                in.close();
            } catch (MalformedURLException e) {
                StringWriter sw = new StringWriter();
                e.printStackTrace(new PrintWriter(sw));
                response.append(sw);
                
            } catch (IOException e) {
                StringWriter sw = new StringWriter();
                e.printStackTrace(new PrintWriter(sw));
                response.append(sw);
            }
             return ""+response.toString();
        }
        public boolean isFinished() {
            return finished;
        }
        public String getResponse() {
            return response;
        }
        public String getUrl() {
            return url;
        }
        public void setUrlGetParams(String urlGetParams) {
            this.urlGetParams = urlGetParams;
        }
        public void setPostData(String postData) {
            this.postData = postData;
        }
        public Integer getResponseCode() {
            return responseCode;
        }
    }    
}

 

processing.jsp

<%@ page language="java" contentType="text/html; charset=UTF-8"
    pageEncoding="UTF-8"%>
<%@ page import="java.util.Set"%>
<!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">
<meta http-equiv="refresh" content="4">
<title>Página Procesando</title>
</head>
<body>
<br/><br/>
<span style="font-family : Verdana, Arial;font-size:10px; font-weight : bold;color: #052878;">
    Processing slow request, please wait...
</span>
<br/>
<div id="container" style="width:99%; height:5px; border:1px solid black;">
  <div id="progress-bar" style="width:10%;background-color: #E5E6F0; height:5px;"></div>
</div>
<script>
  var width = 0;
  window.onload = function(e){
    setInterval(function () {
        width = width >= 100 ? 0 : width+5;  
        document.getElementById('progress-bar').style.width = width + '%'; }, 200);            
  };
</script>
</body>

 

Notes