Hello everyone

Some time ago, I have bumped into a scenario that required me to access an OData on-premise service (built with NW Gateway). The problem arose when I wanted to use it from within a HANA native application, because according to help.hana.ondemand.com and some other blog posts on scn this was not possible.

I have found a work-around to allow developers to access such services. Please note that it should never be used in a productive scenario (mainly because of performance and security issues). In a productive scenario, you can use the connectivity service directly.

There are two ways to do this:

  • Either deploy a Java HTTP Proxy servlet and connect XS with that servlet via a HTTP XS destination.
  • Or create a HTML5 app and use the dispatcher to forward your requests

I will provide an end-to-end guide for both scenarios (although the ideas themselves are very simple).

Exposing the resources with HCC

Before starting to do anything, we must install HCC on-premise (or on a computer which has access to the resources). See the docu for more info 🙂 .

In the HCC, the host on which the OData service is located must be mapped to a virtual host and the OData service path must be exposed or even the root of all OData services. Example:

/wp-content/uploads/2015/05/0_hcc_699048.png

Once the resources have been exposed to the cloud, a destination must be created in the connectivity service (using the cockpit).

This is an example of such a destination configuration:

/wp-content/uploads/2015/05/1_hcc_699049.png

[Option 1]Creating the HTTP Proxy Servlet

The idea behind this kind of servlet is simple: map all HTTP requests to the servlet path (and subpaths) to a predefined path in the previously added destination. Request headers, body and URL parameters must all be passed through and the response must be simply mirrored. Here is a very basic implementation of this kind of servlet:


/**
* Note: this class depends on org.apache.http and org.apache.http.client
* packages. They can be found on <a
 * href="http://mvnrepository.com/artifact/org.apache.httpcomponents">Maven</a>
* (httpclient and httpcore artifacts).
*
* @author Serban Petrescu
*
*/
public class DemoServlet extends HttpServlet {
  private static final int COPY_CONTENT_BUFFER_SIZE = 1024;
  private static final String DEMO_DEST = "demo";
  /**
  * Fill in the <<service_name>> placeholder with they OData service name.
  */
  private static final String SERVICE_ROOT_PATH = "/sap/opu/odata/sap/<<service_name>>/";
  @Override
  public void doGet(HttpServletRequest request, HttpServletResponse response)
  throws ServletException, IOException {
  HttpClient httpClient = null;
  try {
  HttpDestination destination = (HttpDestination) new InitialContext()
  .lookup("java:comp/env/" + DEMO_DEST);
  httpClient = destination.createHttpClient();
  // Build the backend request URI from the original URI
  HttpGet httpGet = new HttpGet(SERVICE_ROOT_PATH
  + request.getRequestURI().substring(
  request.getContextPath().length())
  + (request.getQueryString() == null ? "" : "?"
  + request.getQueryString()));
  // Forward the headers (a white-listing system could be added
  // here...)
  Enumeration<String> headerNames = request.getHeaderNames();
  while (headerNames.hasMoreElements()) {
  String header = headerNames.nextElement();
  String value = request.getHeader(header);
  httpGet.setHeader(header, value);
  }
  // Execute the backend request
  HttpResponse httpResponse = httpClient.execute(httpGet);
  // forward the response status code and headers
  response.setStatus(httpResponse.getStatusLine().getStatusCode());
  if (httpResponse.getStatusLine().getStatusCode() == HTTP_OK) {
  Header[] headers = httpResponse.getAllHeaders();
  for (Header h : headers) {
  response.setHeader(h.getName(), h.getValue());
  }
  }
  // forward the response body
  HttpEntity e = httpResponse.getEntity();
  if (e != null) {
  InputStream in = e.getContent();
  OutputStream out = response.getOutputStream();
  byte[] buffer = new byte[COPY_CONTENT_BUFFER_SIZE];
  int len = in.read(buffer);
  while (len != -1) {
  out.write(buffer, 0, len);
  len = in.read(buffer);
  }
  in.close();
  out.close();
  }
  } catch (DestinationException e1) {
  throw new ServletException(e1.getMessage());
  } catch (NamingException e1) {
  throw new ServletException(e1.getMessage());
  }
  }
}




The servlet must be included into a Java app, deployed and run. Deployment descriptor example:


<?xml version="1.0" encoding="UTF-8"?>
<web-app xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns="http://java.sun.com/xml/ns/javaee"
xmlns:web="http://java.sun.com/xml/ns/javaee"
xsi:schemaLocation="http://java.sun.com/xml/ns/javaee http://java.sun.com/xml/ns/javaee/web-app_2_5.xsd"
id="WebApp_ID" version="2.5">
  <servlet>
    <servlet-name>DemoServlet</servlet-name>
    <servlet-class><!-- class qualified name --></servlet-class>
  </servlet>
  <servlet-mapping>
    <servlet-name>DemoServlet</servlet-name>
    <url-pattern>/*</url-pattern>
  </servlet-mapping>
  <resource-ref>
    <res-ref-name>demo</res-ref-name>
    <res-type>com.sap.core.connectivity.api.http.HttpDestination</res-type>
  </resource-ref>
</web-app>





When accessing the application URL of the application, the OData Service description should be shown. For example:

/wp-content/uploads/2015/05/0_java_699421.png


[Option 2]Creating the HTML5 app

This is even more simple than the Java app alternative. It also has the added benefit of not blocking the account’s compute unit (i.e. we can have several HTML5 apps running in the same time, but only 1 Java app).

The app should contain only one file: the neo-app.json file:


{
  "routes": [
    {
      "path": "/odata/",
      "target": {
        "type": "destination",
        "name": "demo",
        "entryPath": "/sap/opu/odata/sap/<<service_name>>/"
      }
    }
  ],
    "authenticationMethod": "none"
}




Deploy, create a version and activate the app. Now, when accessing the <<application_path>>/odata/ URL, the service description should pop-up.


Result example:

/wp-content/uploads/2015/05/0_html5_699419.png


Consuming the OData with XS

An XS native app must be created and a xsdestination file must be make, pointing towards the Java servlet base path.

Application file (just prints the response):


var destination_package = "<<location_of_dest_package>>";     //e.g. p1234567890trial.demo.demoapp
var destination_name = "<<dest_name>>";          //e.g. demo
try {
       var dest = $.net.http.readDestination(destination_package, destination_name);
       var client = new $.net.http.Client();
       var req = new $.web.WebRequest($.net.http.GET, "$metadata");
       client.request(req, dest);
       var response = client.getResponse();
    $.response.contentType = "application/json";
       $.response.setBody(response.body.asString());
       $.response.status = $.net.http.OK;
} catch (e) {
       $.response.contentType = "text/plain";
       $.response.setBody(e.message);
}




Destination file (e.g. demo.xshttpdest):


host = "<<app_host>>"; //e.g.: test-p1234567890trial.dispatcher.hanatrial.ondemand.com
port = 443;
pathPrefix = "<<app_root_path>>"; //should be filled with the root path of the Java / HTML5 app.
     //this path is concatenated with the host to obtain the absolute application path
     //Java: /<<application_name>>/<<servlet_path>>      e.g.: /demo/ (servlet root is /*)
     //HML5:: /<<dest_route_path>>/                                    e.g.: /odata/
useProxy = true;
proxyHost = "proxy-trial";
proxyPort = 8080;
authType = none;     //could theoretically use saml
useSSL = true;
timeout = 30000;




Once the XS app is up, the default trust store must be assigned (Cockpit -> XS Apps -> Select they application -> Assign Trust Store -> DEFAULT). After this step, running the app should print the service metadata. Something like this:

/wp-content/uploads/2015/05/1_xs_699420.png

And that’s it 🙂 This would work for any on-premise HTTP resources exposed through HCC. If RFC’s are needed, they can be called through a Java app and then exposed using HTTP with a similar mechanism as above.

To report this post you need to login first.

Be the first to leave a comment

You must be Logged on to comment or reply to a post.

Leave a Reply