A common solution to calling an outside web service is to utilize server-side code to make the request. This effectively utilizes a proxy (where the server-side code [PHP, Ruby, C#, etc] serves as the proxy, making the call to the 3rd-party web service on behalf of the client-side script). You’ve probably implemented this solution without even realizing that you were handling the cross-domain policy. Typical examples might include calling a weather API web service, Google service, Flickr, data scraping a web page, or other outside web calls. In each request from the client script, a call was made back to the local web application to fetch data, process it, and return it to the local client web browser. The web application was serving as a proxy between the web browser and outside domain.
Using server-side code as a proxy is not always an option. In particular, making a call to the local web application, which in turn, retrieves the data from the outside domain and returns it to the client, may be an unnecessary and extraneous call, creating a bottle-neck and potential performance degradation. To optimize the process, the call to the local web application could be bypassed and the client script could call the outside web service directly. This would offload your own web server and move the processing to the client-side.
In both scenarios above, the web browser would be prevented from making the request due to the cross-domain policy, which prevents requests to outside domains from the web browser. To get around this issue, you’ll need to use AJAX with JSONP.
To demonstrate the cross-domain policy and potential solutions, we’ll create a simple web service using C# ASP .NET MVC. Our web service will create a random monster and return its data in JSON format. Our MonsterController can be implemented as follows:
In the above code, we simply check if the request is an asynchronous request. If it’s not, we return the default view. If it is an AJAX request, we create a random monster and return it in JSON format. Calling the above method (ie., “/Monster”) would return the following result to the client script:
The MonsterController MVC view
We can create a simple web page view to render the monster, with the following Razor markup code:
This will utilize jQuery to make an XMLHttpRequest call to retrieve the data from our local web service method. Running the sample would produce the following output:
As you can see in the example, our client script was able to successfully load and display the monster data from the local web service. Since the cross-domain policy was not violated, we were able to call the local web service and return the result.
Let’s take a look at what happens if we try to call an outside domain with the same client-script code. We can simulate an outside domain by modifying our hosts file. Locate your hosts file in C:\Windows\System32\drivers\etc and edit the file “hosts” to add a new entry. You can redirect any domain to 127.0.0.1, which is your local PC. All requests to this domain will now be sent to your local PC (and thus, your local web application), although the web browser will still believe this is an outside domain.
Modifying the local PC’s hosts file to simulate an outside web server and cross-domain request:
By modifying our hosts file, we’ve effectively setup an outside domain name that calls our local web service (for debugging purposes). You could also copy your web service application to a web server with actual hosting in order to test this, although modifying the hosts file is an easier step.
JSONP is a technique that utilizes JSON to circumvent the cross-domain policy and allow client script to communicate with outside web services on different domains. JSONP takes advantage of the fact that while web browser cross domain policy prevents data and documents from being modified on outside domains, it does not prevent script from being downloaded from outside domains. Using this knowledge, JSONP requests web service data, wrapped within a script tag. The source url of the script is the target web service url. The resulting script is then executed by a callback handler in the client, where we can then process the actual data.
Of course, your client script would need to define the processData(data) method. Rather than impose function names on client developers, the JSONP standard is to include a “callback” parameter in the calling url, to indicate the name of the wrapped method. For example:
The client uses:
The web service responds with:
Your resulting client script then defines “myMethod(data)” and handles the resulting data accordingly. Note, jQuery’s getJSON() method takes care of automatically including the callback parameter and even creating a random callback method to grab the data for you.
The modified MVC MonsterController class, using JSONP as the resulting data:
With the above changes made, we can try calling our web service on an outside domain again. However, we first need to make a change to our client script to make it compatible with JSONP requests. This includes using a script tag with the target url being our web service call. We’ll utilize jQuery to streamline this for us, as follows:
In the above code, we’ve modified our call to the jQuery ajax() method to include a “dataType” parameter of “json”. This tells jQuery to output a script tag and fill in our callback parameter with a random function name. The resulting data will be processed and returned in the “success” method. This is the same manner of reading the returned data as we’ve used above. Note, we no longer need to include a ticks parameter in the url, to avoid the cache, since the callback parameter will be filled in automatically with a random string.
Running the above code would produce the following output:
We’ve successfully bypassed the cross-domain policy, reading and processing data from an outside domain. We can actually take the code one step further in optimization, as follows:
We’ve replaced the cumbersome jQuery .ajax() method with a call to .getJSON(), which automatically inserts the “dataType: json” property and streamlines the success method.
JSONP permits a powerful techinque for writing widget applications that can read data from outside web services with ease. However, it also contains some drawbacks and security considerations. The first item of concern is that JSONP typically provides no error handling from broken links or malformed data. The call will typically simply fail. This can make it difficult to debug problems and recognize errors. Of course, client code can always timeout on a request to avoid prolonged delays, it’s still important to keep in mind error handling issues.
The source code for this example is available online in our GitHub repository.
This article was written by Kory Becker, software developer and architect, skilled in a range of technologies, including web application development, machine learning, artificial intelligence, and data science.