Or as the subtitle could also read: Overcoming "Access-Control-Allow-Origin"- errors
Setup in this example is:
- development machine with local (web) server running a WebApp from http://localhost:12345
- Gateway server accessible under a different domain than the local development machine, such as http://example.gateway.com:8000
- need to consume services onGateway server, originating from local machine => Cross-Domain Request!
There's a lot written out there why Cross-Domain read requests (not write requests!) aren't a good idea. They generally open up data transfer possibilities between domains. Which is something you don't necessarily want - for production environments, that is. What you should also never cater to - use a proxy-based approach to consolidate data read access under one domain, either software-based or with middleware.
But what about development?
There are legitimate reasons to read data across domains in development scenarios. Not the least is if you want to work with features offered by SAP Gateway - accessing SAP Backend logic that is exposed via OData Services. Which means issuing cross-domain REST requests from your development machine to a Gateway instance. If you don't have Gateway running on your development machine. Which is unlikely. So here we are...
The good and the caveat - SOP
First of all, in the Web's overall architecture, there's SOP or Same-Origin-Policy:
[It] restricts how a document or script loaded from one origin can interact with a resource from another origin.
Origin here means the combination of protocol (e.g. http), FQDN (e.g. my.domain.com) and port (e.g. 80). So https://www.js-soft.com:4711 is considered an origin - so would http://localhost:12345.
And "can interact" in the quote above should be read as "scripts running from one origin are generally forbidden to interact with scripts from another origin". (Does that mean you can't include content from different sites? No! Read on...)
The client side
Stir in some OData- and Gateway-spice and it adds up to the following:
The user agent (e.g. Firefox) is rendering the WebApp from origin1, let's say running on your development machine at http://localhost:12345/.
Now, if the WebApp is trying to retrieve JSON or XML via OData from http://example.gateway.com:8000/sap/opu/odata/sap/ZService/Entity(1), the user agent will send an "XMLHttpRequest cannot load"-error and block the request:
XMLHttpRequest cannot load http://example.gateway.com:8000/sap/opu/odata/sap/ZService/Entity(1).
No 'Access-Control-Allow-Origin' header is present on the requested resource.
Origin 'http://localhost:12345/' is therefore not allowed access.
This is due to SOP being implemented in all major user agents: the scripts in the WebApp are forbidden to consume resources from origins other than (in our example) http://localhost:12345/. Retrieving OData from the same origin such as http://localhost:12345/the/odata/endpoint would be allowed, retrieving http://example.gateway.com:8000/sap/opu/odata/sap/ZService/Entity(1) is not.
Note that we're talking run-time and client-side here. SOP doesn't apply to including JavaScript-files per se, it applies to the run-time interpretation of the script itself. So if you include JS-files via the <script>-tag, (e.g.<script src="https://sapui5.hana.ondemand.com/resources/sap-ui-core.js">), the JS-file will be downloaded from the remote address first. Then it will run from your origin, being interpreted by a user agent - that's the moment that SOP restrictions will apply, not earlier.
Disclaimer: SOP is generally a Good Thing(tm). In prevents scripts from origin A to read data from origin B and transfer it back to A.
Which is exactly what you'd like to do in development
Resolution: start-flag for Chrome
Google Chrome offers a way to turn off SOP.
If you start the binary with the switch --disable-web-security, SOP gets disabled, allowing client-side cross-domain requests.
E.g. on OS X:
/Applications/Google\ Chrome.app/Contents/MacOS/Google\ Chrome --disable-web-security
On Windows:
"C:\Program Files (x86)\Google\Chrome\Application\chrome.exe" --disable-web-security
We're half-way there - cross-domain requests are allowed on the client-side now.
The server side
Even though the client now allows requests across domain boundaries, the server still needs to grant access to those requests.
A standard way of doing this is utilizing CORS or Cross-Origin Resource Sharing on the server-side.
In essence, this means sending a an "Access-Control-Allow-Origin" header back to the client, authorizing the client's domain - or granting general access with '*'.
Note: '*'-access is exactly what all the Northwind OData Test Services do. And what explains their popularity in example code.
Resolution: CORS with Gateway
Per default, Gateway send similar headers such as these:
Content-Encoding:"gzip"
Content-Length:"43233"
Content-Type:"text/html; charset=utf-8"
Server:"SAP NetWeaver Application Server / ABAP 731"
dataserviceversion:"2.0"
sap-metadata-last-modified:"Tue, 08 Jul 2014 08:55:38 GMT"
In order to send an additional, custom header from a Gateway-service, use set_header from Interface /iwbep/if_mgw_conv_srv_runtime.
It takes a structure as argument, consisting of a key-value pair.
data: ls type ihttpnvp. ls-name = 'Access-Control-Allow-Origin'. ls-value = '*'. /iwbep/if_mgw_conv_srv_runtime~set_header( is_header = ls ).
This will result in the desired CORS "Access-Control-Allow-Origin" header, granting all clients ("*") read access:
access-control-allow-origin:"*"
Conclusion
Bringing all the above together means that by
- using a switch to Google Chrome, you can get around SOP on the client side
- sending the 'Access-Control-Allow-Origin' header from the Gateway-service allows CORS on the server side
And there you are, hopefully hacking away happily on your local machine, calling Gateway OData back and forth
For development purposes only - don't unhinge these web security fundamentals just to make a quick transition into production scenarios, please!
tl;dr: Chrome --disable-web-security disables SOP, Gateway /iwbep/if_mgw_conv_srv_runtime~set_header allows CORS => developer happy