Saturday, August 3, 2013

Cross domain Request using jQuery and Java

There are situations when we need to make request to other domains and then execute a callback function. Let’s say you are on mysite.com and want to make a request to someapi.com. This is not allowed by most of the modern browsers due to Same origin policy.

Let’s consider an example of  an AJAX request to a cross domain server. We know jQuery provides response callback methods for it. You’ll notice that you are able to make the AJAX call, the server is receiving the request, but the response callback functions do not execute. There are a couple of ways to overcome this and make cross-domain requests possible.
  • JSONP callback.
  • Cross-origin resource sharing (CORS).

JSONP callback

According to Wikipedia, JSONP or "JSON with padding" is a communication technique used in JavaScript programs which run in Web browsers. The Same origin policy is applicable to all elements of a web page, except for the <script> tag. JSONP makes use of this open policy to retrieve arbitrary JavaScript code and not JSON. The JavaScript code is then evaluated by the JavaScript interpreter, and not parsed by a JSON parser.

A JavaScript function to make JSONP calls:
The server has to send a response, which when evaluated by the JavaScript interpreter calls the ‘callback’ function.

String jsonpCall = request.getParameter("callback");
out.println(jsonpCall + "('success')"); // where out = response.getWriter();
// Reponse => callback('success')


There are some disadvantages of JSONP:-
  • As you see the callback is called dynamically and there are no events that handle the callback, all errors have to be handled manually. Since, jqXHR.success(), jqXHR.error()wont work. The best way described to do this in many forums is to have an timeout. Which in any case is not an 100% solution.
  • It only supports GET request.

Cross-origin resource sharing (CORS)

CORS is a mechanism in which the browser and the server can interact to determine whether or not to allow the cross-origin request. Details.
In this process, we just need to allow a particular origin to send cross domain request. We do this by adding  a response header to the response. After, adding the header it works like any other request of the same domain. Thus, it supports all jqXHR(jQuery XMLHttpRequest) response callback methods. It also supports POST request.

// If you want to allow http://192.168.1.2:8080/BlogExamples/origin.html
response.addHeader("Access-Control-Allow-Origin", 
    "http://192.168.1.2:8080/BlogExamples/origin.html");
// or to allow all origins.
response.addHeader("Access-Control-Allow-Origin", "*");

The only problem with this approach it requires you to have access to the server code.

Putting it all together:-

Client code: origin.html
<!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">
<script src="jquery-2.0.3.min.js"></script>
<title>Origin</title>
</head>
<body>
    <button name="jsonCallback" onclick="jsonp()">JSONP</button>
    <button name="cors" onclick="cors()">CORS</button>
    <script type="text/javascript">
        function jsonp() {
            $.ajax({
                url : 'http://localhost:8080/BlogExamples/ResourceServlet',
                dataType : 'jsonp',
                jsonpCallback : 'callback',
                data : {
                    method : 'jsonp',
//                    jsonpCallback : 'callback'
                }
            });
        }

        // JSONP Callback
        function callback(response) {
            alert("Response from cross origin: " + response);
        }
        function cors() {
            $.ajax({
                url : 'http://localhost:8080/BlogExamples/ResourceServlet',
                type : 'POST',
                data : {
                    method : 'cors'
                },
                success : function(response) {
                    alert("Response from cross origin: " + response);
                },
                error : function(jqXHR, textStatus, error) {
                    alert(error);
                }
            });
        }
    </script>
</body>
</html>

Server code: ResourceServlet.java
package com.web;
import java.io.IOException;
import java.io.PrintWriter;
import javax.servlet.ServletException;
import javax.servlet.annotation.WebServlet;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
/**
* Servlet implementation class ResourceServlet
*/
@WebServlet("/ResourceServlet")
public class ResourceServlet extends HttpServlet {
    private static final long serialVersionUID = 1L;
    protected void doGet(HttpServletRequest request,
            HttpServletResponse response) throws ServletException, IOException {
        process(request, response);
    }
protected void doPost(HttpServletRequest request,
            HttpServletResponse response) throws ServletException, IOException {
        process(request, response);
    }
protected void process(HttpServletRequest request, HttpServletResponse response)
            throws ServletException, IOException {
        // Checks the the AJAX function calling
        if ("jsonp".equals(request.getParameter("method")))
            callbackResponse(request, response);
        else
            corsResponse(request, response);
    }
private void callbackResponse(HttpServletRequest request,
                HttpServletResponse response) throws IOException {
        response.setContentType("text/html;charset=UTF-8");
        String jsonpCall = request.getParameter("callback");
        PrintWriter out = response.getWriter();

        try {       
            out.println(jsonpCall + "('success')");
        } catch (Exception e) {
            out.println(jsonpCall + "('error')");
        } finally {
            out.flush();
            out.close();
        }
    }

    private void corsResponse(HttpServletRequest request,
            HttpServletResponse response) throws IOException {

        response.setContentType("text/html;charset=UTF-8");
        response.addHeader("Access-Control-Allow-Origin", "*");
        PrintWriter out = response.getWriter();
        try {
            out.println("'success'");
        } catch (Exception e) {
            out.println("'error'");
        } finally {
            out.flush();
            out.close();
        }   
    }
}