Cross-domain communications with ABAP and JSONP
In this blog i want to create an ABAP Custom HTTP handler using JSONP, a pattern of usage that allows a page to request data from a server in a different domain.
As a solution to this problem, JSONP is an alternative to a more recent method called Cross Origin Resource Sharing ( http://en.wikipedia.org/wiki/JSONP ). Our ABAP Http service will generate Javascript code that will call the callback function and pass in a JSON containing Names of Abap Classes (a kind of SE24 Matchcode)
RESTful services and json are a great way to get Sap data into a website like an Intranet Homepage or any other external websites.
However,we have to consider that Under the same origin policy there is no way to make a request to a different domain from JavaScript using AJAX because of security restrictions. I really liked the blog “Deliver dynamic search of SAP data into a website using RESTful services” by Sap Mentor John Moy (Deliver dynamic search of SAP data into a website using RESTful services) .
Especially , I was very impressed about CORS (Cross Origin Resource Sharing,follow this good link from post by John to learn about CORS ( http://bit.ly/9iT2Na ).
CORS allows method GET and POST , it uses regular XMLHttpRequest but it’snt supported by all browser (Firfox 3.5+,Safari 4+,Chrome,partially in IE8). Another way to request data from a server in a different domain is JSONP. JSONP is a script element injection into the DOM , so our Abap Service Url will be included dinamically between using Javascript. What JSONP does is add a element to the DOM, with the external URL (in this case,an Abap Service URL) as the SRC target.
However, i think that JSONP is a temporary solution until full support for CORS that will be the definitely way. Below the example:
Part 1 : HTML / Javascript
function showAbapClasses(jsonAbap){
var htmlString = "<br/><div>Abap Response:</div>";
for (var i = 0; i < jsonAbap.classes.length; i++) {
htmlString += "<br/>" + jsonAbap.classes[i];
}
document.getElementById("results").innerHTML = htmlString;
}
The Abap response will be :
showAbapClasses({classes:["CL_ABAP_GZIP","CL_ABAP_GZIP_BINARY_STREAM",
"CL_ABAP_GZIP_TEXT_STREAM","CL_ABAP_UNGZIP_BINARY_STREAM",
"CL_ABAP_UNGZIP_TEXT_STREAM","CL_ABAP_ZIP"]});
The javascript code injection into the DOM:
var head = document.getElementsByTagName("head")[0];
var script = document.createElement("SCRIPT");
script.type = "text/javascript";
script.src = "http://sapfqdn:8000/zjsonp?q=" +
encodeURIComponent(document.getElementById("input_field").value)
+ "&callback=showAbapClasses";
script.id = "JSONP";
head.appendChild(script);
Our script element dynamically Inserted into the DOM (our Abap SICF service) will be:
http://sapfqdn:8000/zjsonp?q=CL*ABAP*ZIP&callback=showAbapClasses
Javascript complete code
//12.05.2011
//Alessandro Spadoni
//this is just demonstration code that i'm using to illustrate abap & jsonp
function showAbapClasses(jsonAbap){
var htmlString = "<br/><div>Abap Response:</div>";
for (var i = 0; i < jsonAbap.classes.length; i++) {
htmlString += "<br/>" + jsonAbap.classes[i];
}
document.getElementById("results").innerHTML = htmlString;
}
function abap_jsonp() {
if (document.getElementById("input_field").value){
// Remove any old script tags.
var script;
while (script = document.getElementById("JSONP")) {
script.parentNode.removeChild(script);
// Browsers won't garbage collect this object.
// So castrate it to avoid a major memory leak.
for (var prop in script) {
delete script[prop];
}
}
var head = document.getElementsByTagName("head")[0];
var script = doPcument.createElement("SCRIPT");
script.type = "text/javascript";
script.src = "http://sapfqdn:8000/sap/zjsonp?q=" + encodeURIComponent(document.getElementById("input_field").value)
+ "&callback=showAbapClasses";
script.id = "JSONP";
head.appendChild(script);
}
Part 2 : ABAP
The Abap HTTP Handler will generate JAVASCRIPT concatenating the callback function and the JSON object. In this example I created Json using a simple concatenate statement.
method IF_HTTP_EXTENSION~HANDLE_REQUEST.
**12.05.2011
**Alessandro Spadoni
**this is just demonstration code that i'm using to illustrate abap & jsonp
DATA: method TYPE string,
query_string TYPE string,
callback TYPE string,
json_response TYPE string,
t_classname TYPE TABLE OF seoclsname,
r_classname TYPE RANGE OF seoclsname,
l_classname LIKE LINE OF r_classname,
l_lines LIKE sy-tfill.
FIELD-SYMBOLS: <classname> TYPE seoclsname.
method = server->request->get_header_field( name = '~request_method' ).
query_string = server->request->GET_FORM_FIELD_CS( name = 'q' ).
callback = server->request->GET_FORM_FIELD_CS( name = 'callback' ).
**method not allowed
IF method <> 'GET'.
server->response->set_status( code = '405' reason = 'You must use GET' ).
EXIT.
ENDIF.
**bad request
IF query_string IS INITIAL.
server->response->set_status( code = '400' reason = 'Query String is initial' ).
EXIT.
ENDIF.
IF callback IS INITIAL.
server->response->set_status( code = '400' reason = 'Callback is initial' ).
EXIT.
ENDIF.
**fill range
l_classname-sign = 'I'.
l_classname-low = query_string.
IF query_string CS '*'.
l_classname-option = 'CP'.
ELSE.
l_classname-option = 'EQ'.
ENDIF.
APPEND l_classname TO r_classname.
**example -> max 25 rows
SELECT clsname INTO TABLE t_classname UP TO 25 ROWS FROM seoclass
WHERE clsname IN r_classname.
DESCRIBE TABLE t_classname LINES l_lines.
CONCATENATE callback '({classes:[' INTO json_response.
LOOP AT t_classname ASSIGNING <classname>.
CONCATENATE json_response '"' <classname> '"' INTO json_response.
IF sy-tabix < l_lines.
CONCATENATE json_response ',' INTO json_response.
ENDIF.
ENDLOOP.
CONCATENATE json_response ']});' INTO json_response.
server->response->set_status( code = '200' reason = 'OK' ).
server->response->set_header_field( name = 'Content-Type' value = 'application/javascript' ).
server->response->set_cdata( data = json_response ).
endmethod.
well done with the blog demonstrating how customers can do 'cool' AJAXy webpages and integration right now, in their existing systems, and without additional software!
I also like the flow diagram - it really helps explain what's going on here.
Thank you for sharing!
Sascha
i'm glad you appreciated!(just found you on twitter)
Alessandro
Great blog, and thanks for the mention. I did investigate JSONP, but didn't get to around to implementing it so I appreciate your thoroughly documented example.
I should mention that my own investigations into CORS came about because of a comment by Chris Paine about it as a response to one of my earlier blogs. Now you've covered off the JSONP approach. I guess that's the community at work!
Well done.
John