Technology Blogs by SAP
Learn how to extend and personalize SAP applications. Follow the SAP technology blog for insights into SAP BTP, ABAP, SAP Analytics Cloud, SAP HANA, and more.
cancel
Showing results for 
Search instead for 
Did you mean: 
roye_cohen
Discoverer

What is the problem?

While developing SAP HANA Cloud Portal as a platform for widgets development, we have stumbled upon a major issue with cross origin requests (like many platforms). The issue is that the widget's iframe is loaded by Cloud Portal server, and if a request is being sent to the widget's app server, it will be blocked by the browser according to the same origin policy (Described here: http://tools.ietf.org/html/rfc6454#section-3). We observed a few solutions for this issue, and came up with the server proxy solution. According to the solution, each and every request that is sent to any server different from the portal (the common case) will be 'proxified' using the portal's proxy server.

In detail

For this mission, we had to manipulate widget URLs. First, we have to turn relative URLs to be absolute and then use the proxy if they point to a different origin. URLs in this manner are divided into two categories: HTML tag URLs (such as 'src' attribute value) and Javascript URLs (such as AJAX request URLs). As for the first one, we used Shindig server's rewriting engine in order to rewrite URLs and direct them through our proxy (done on the server side). In order to manipulate the second type, we have developed an OpenSocial feature ('sap-xhrwrapper') that will override native AJAX 'open' method, which will include the manipulations there (done on the client side). In addition, there are some DOM elements that are being created dynamically using Javascript code. For these we are using jQuery in order to manipulate their URL upon load. Elements that are generated on runtime will not be rewritten on the server (since they do not  exist yet), nor by the AJAX wrapper (since they are not using AJAX). For this case the portal listens to each DOM element creation which has 'src' attribute (using jQuery), and wraps it with the same logic as it wraps the AJAX requests.

Example

Let's dive in with a real life example that includes the basic structure of a widget: Spec XML, HTML and Javascript files.

Let's assume that portal's domain is 'https://portal.com', and widget's URL is 'https://widget.app.com'.

Then widget's iframe URL should look like that: 'https://portal.com/shindig/gadgets/ifr?url=https%3A%2F%2Fwidget.app.com%2Fwidget%2Fspec.xml'. Let's say that this is the content of the widget:

spec.xml

<?xml version="1.0" encoding="UTF-8" ?>

<Module>

  <ModulePrefs title="My Widget">

    <Require feature="sap-xhrwrapper" />

  </ModulePrefs>

  <Content type="url" href='index.html'></Content>

</Module>

  1. index.html

<html>

<head>

       <script src='js/app.js' />

</head>

       <div id='content'></div>

</html>

app.js

jQuery.get('/service/data', function onSuccess(res) {

       jQuery('#content').html(res.data);

       jQuery('#content').append('<img src="/img/logo.png" />');

});

So right now, each request that will be sent from this iframe to a server that is different from 'https://portal.com' will be blocked by the browser. This means that the AJAX request in 'app.js' file will be blocked. Let's get into the flow step-by-step and see what is being done under the hood.  First, since the widget's spec XML file should be always placed in the same directory with the HTML of the application (this is a current limitation), and we know what is the widget's domain from the iframe URL, we will turn 'index.html' to 'https://widget.app.com/widget/index.html' on the server side as part of widget rendering process. Next, we manipulate the 'script' tag 'src' attribute (if we will not do that, it will be fetched from 'https://portal.com/shindig/gadgets/js/app.js' by the browser, which is not the right URL of course). So again here, we have the widget's domain and it will help to turn 'js/app.js' to 'https://widget.app.com/js/app.js'.

We are done now with the widget's markup, so that the returned HTML will look like the following:

<html>

<head>

       <script src='https://widget.app.com/widget/js/app.js' />

</head>

       <div id='content'></div>

</html>

Next, the browser loads the Javascript file and executes its content, which is just an AJAX call in this case. Since we are using 'sap-xhrwrapper' feature here (as you can see in the spec XML), the AJAX behavior is overridden. We have the widget domain on the window object (recall that the 'src' of the iframe points to 'https://portal.com/shindig/gadgets/ifr?url=https%3A%2F%2Fwidget.app.com%2Fwidget%2Fspec.xml'), so we can make the relative URL absolute: 'https://widget.app.com/service/data'. Notice that the URL points to a remote server that is not the portal, so we will have to wrap it within a proxy call. All we have to do is to encode this URL and embed it inside the proxy URL pattern, which will lead to the final URL: 'https://portal.com/shindig/gadgets/proxy?container=default&url=https%3A%2F%2Fwidget.app.com%2Fservice%2Fdata'. Now the request will not be blocked because it's sent to same origin URL, and the data will be fetched from the widget's app backend correctly!

Summary

We saw how Cloud Portal is coping with same origin policy as platform for application (widget) development. By solving this issue Cloud Portal enables smooth migration of your standalone application into the portal. There are a few types of rewriting taking place on a widget's lifecycle: HTML URLs, AJAX URLs and dynamic DOM element URLs. As a widget developer it's important to be aware of this process, in order to understand and debug your application well. Our goal is to be unobtrusive as much as we can, and not interfere in the development process with specific demands for Cloud Portal.

We would like to hear from you. If you have any question of suggestion, don't hesitate to contact us.

Enjoy!