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: 
jens_rannacher
Explorer
The SAP Data Hub Pipeline Engine offers different ways to serve a RESTful API from within a pipeline. In general, this can be achieved by using one of the predefined SAP Data Hub Pipeline Operators or by implementing a custom operator that exposes a REStful server:

  • HTTP Server Operator: This operator starts an HTTP server on a configurable port.

  • OpenAPI Servlow Operator: A convenient server-side component that is suitable for providing services described in Swagger/OpenAPI or unspecified HTTP services.

  • Custom Operator: For example Python (using Flask).


All three approaches have their advantages and disadvantages, however, one challenge that all three have in common is providing a stable endpoint for the server that can be accessed from outside of the Kubernetes cluster.

During runtime, pipeline operators are executed within Docker containers and without further adjustments, the ports exposed by each operator are only reachable within the Docker container or the Kubernetes Pod resp. Moreover, the Kubernetes Node on which a Pod is scheduled is not stable, i.e. the Kubernetes Pod IP address changes once the corresponding SAP Data Hub Pipeline is restarted. This has to be considered when the RESTful service running in a pipeline shall be accessible from outside the Kubernetes cluster.

In the following, I will explain three different ways of implementing a RESTful service using SAP Data Hub Pipelines and how these services can be accessed externally. The functionality described in this blog is based on SAP Data Hub Version 1.0 SPS03.

HTTP Server Operator


In SAP Data Hub Version < 2.5.



The predefined HTTP Server operator starts an HTTP server on a configurable port. It handles GET and POST requests received from a configurable handler path. The operator supports the following HTTP methods:

  • GET: Send JSON-formatted data as a response to a GET request. The data included in the response is all the data that was received via the input port of the operator. There is no way to reset the response body or to send a different response based on parameters in the GET request. For this reason, the GET-method is currently only suited for scenarios where a static response body shall be returned.

  • POST: Allows to receive the JSON-formatted body of a POST request. The body is directly forwarded to the output port of the operator and upon success, an empty body with status code 200 is returned. It is not possible to send a different body in the response of the POST-request. Therefore, this method is only suitable for scenarios where data is sent to a pipeline whereas acknowledgment based on the content is not required.


Example Pipeline using HTTP Server Operator


The following pipeline shows how the HTTP Server Operator can be used to handle GET- and POST-requests that are sent from outside the cluster:



The pipeline consists of the following operators:

HTTP Server


This operator starts an HTTP server on port 7070 and serves GET- and POST-requests on handle path "/":



All data received from the operator ToBlob Converter is returned in the response of the GET-request and the data received in the body of a POST-request is forwarded to the operator Wiretap.

Constant Generator


The Constant Generator sends (once) the JSON string {"some":"response"} to the input port of the HTTP Server operator:



This string is returned by the HTTP Server operator in the response body of all GET-requests.

Reverse Proxy


The operator Reverse Proxy is basically a Terminal operator with a custom route of type rproxy that forwards all external requests on path /service to the HTTP Server listening on localhost:7070:



(All operators are running in the same default group and therefore within the same Pod/Container). Without the reverse proxy route specified above, the HTTP Server would not be reachable from outside the Pod or the Kubernetes cluster resp.:

Wiretap


The Wiretap operator is just used to display the payload that is sent via the body of the POST requests.

Interaction with the HTTP Server


When running the pipeline, you can discover the URL to the Reverse Proxy by opening the UI of the Terminal operator:



The URL of the Terminal has the following structure:

https://<host>:<vsystem-port>/app/pipeline-modeler/service/v1/graphs/<graph-id>/operator/terminal<id>/

  • host: The hostname of the System Management service.

  • vsystem-port: The port of the System Management service.

  • graph-id: Automatically generated unique if of the pipeline instance.

  • id: Automatically generated number representing the Terminal instance.


The URL changes with every restart of the pipeline. Therefore, you have to lookup and change the URL in the client that interacts with the HTTP server everytime the pipeline is stopped and started.

By appending the Path that was set in the configuration of the Reverse Proxy operator above, you can obtain the URL of the HTTP Server:

https://<host>:<vsystem-port>/app/pipeline-modeler/service/v1/graphs/<graph-id>/operator/terminal<id>/service

With that in place, you can test the HTTP Server, e.g. using Postman:

GET-Request:


The GET-request returns the JSON-string that was specified in the Constant Generator operator above.

Postman example:



Corresponding Curl command:
curl --request GET \
--url https://<host>:<vsystem-port>/app/pipeline-modeler/service/v1/graphs/<graph-id>/operator/terminal1/s... \
--header 'cookie: Authorization="Bearer <token>"' \
--insecure

The authentication token can be obtained via the following URL with basic authentication:
curl -vk -u "tenant\\user:password" https://<host>:<vsystem-port>/auth/login


POST-Request:


The POST-Request returns an empty body with HTTP status code 200. The JSON string in the body of request is {"send":"data"} .

Postman example:



Corresponding Curl command:
curl --request POST \
--url https://<host>:<vsystem-port>/app/pipeline-modeler/service/v1/graphs/<graph-id>/operator/terminal1/s... \
--header 'referer: https://<host>:<vsystem>/app/pipeline-modeler/' \
--header 'cookie: Authorization="Bearer <token>"' \
--header 'x-requested-with: XMLHttpRequest' \
--data '{"send":"data"}' \
--insecure

The authentication token can be obtained via the following URL with basic authentication:
curl -vk -u "tenant\\user:password" https://<host>:<vsystem-port>/auth/login

Once the POST-Request is sent to the server, you should see the following output in the Wiretap Operator:



This example shows how the HTTP Operator can be used to receive data via a POST-request and how to return data to a GET-request. With the Terminal operator and custom reverse proxy route, you can route external requests to the HTTP Server. However, the disadvantage is that the URL is not stable and changes upon restart of the pipeline. In the next example, you will learn how to implement a static endpoint using the OpenAPI Servlow operator.

OpenAPI Servlow Operator




The OpenAPI Servlow Operator currently provides the most convenient way of implementing a RESTful service in an SAP Data Hub Pipeline. The operator is suitable for providing services described in a Swagger/OpenAPI document but can also be used for unspecified HTTP services. The component runs within the web container of the SAP Data Hub Pipeline Engine and inherits its security configuration. More concretely, this operator is configured to start its REST endpoint relative to the Pipeline Modeler's service path at /openapi/service/basePath/. The clear advantage is, that this URL stays stable, even after a pipeline restart.

Example Pipeline using the OpenAPI Servlow Operator


There are two predefined example pipelines shipped with the SAP Data Hub that illustrate the usage of the OpenAPI Servlow Operator very nicely:

  • com.sap.demo.openapi.server.greeter: Example providing a demo greeter service with its swagger document.

  • com.sap.demo.openapi.server.plain_greeter: Example providing a demo greeter service with no swagger document.


In the following, I explain the usage of the OpenAPI Servelow Operator without Swagger document (second example pipeline):



The pipeline consists of the following operators:

OpenAPI Servlow


When a request matching the configured basePath /samples/plain_greeter arrives via HTTP, an output message is generated and sent to the output port to the connected Wiretap operator. The OpenAPI Servlow operator supports both request-response and one-way message exchange patterns (MEPs). When the operator is running in the request-response mode, it waits for the response up to the specified timeout of 300000 ms. If the response message is returned to this operator over the engine's response callback mechanism, it is returned to the HTTP caller. Otherwise, an error is returned to the HTTP caller:



The output message generated by OpenAPI Servlow operator depends on whether it is configured in the plain mode (i.e., no swagger document is specified) or the swagger-driven mode (i.e., a swagger document is specified). In the plain mode, the headers and body of the incoming HTTP request will be directly transferred to the output message. In contrast, in the swagger-driven mode, the matching operation to the incoming HTTP request will be determined and the corresponding parameters for that operation will be extracted. Subsequently, these operation specific parameters will be transferred to the output message.

Wiretap


The Wiretap operator is used to display the messages that are sent by the OpenAPI Servlow operator to the Greeter operator.

Greeter


This is a Javascript Operator that receives the messages from the OpenAPI Servlow operator indirectly via the Wiretap operator. The code below shows how it is used to handle:

  • GET-Requests sent to the relative path /samples/plain_greeter/v1/ping

  • POST-Requests sent to the relative path /samples/plain_greeter/v1/echo


$.setPortCallback("input",onInput);

// for simplicity, no
var count = 0;
var totalgreeted = 0;
var greeted = {};

function isByteArray(data) {
return (typeof data === 'object' && Array.isArray(data)
&& data.length > 0 && typeof data[0] === 'number')
}

function onInput(ctx,s) {
var msg = {};

var inbody = s.Body;
var inattributes = s.Attributes;

// convert the body into string if it is bytes
if (isByteArray(inbody)) {
inbody = String.fromCharCode.apply(null, inbody);
}

// just send a copy of the request to the output for someone else (e.g., if port output is connected)
msg.Attributes = {};
for (var key in inattributes) {
msg.Attributes[key] = inattributes[key];
}
msg.Body = inbody;

// pass a copy to the output if connected
if ($.output != null) {
$.output(msg);
}

// send the response
var reqmethod = inattributes["openapi.method"];
var reqpath = inattributes["openapi.request_uri"];

var resp = {};
resp.Attributes = {};
// as there is no swagger spec configured to specify the responese content-type, set it here
resp.Attributes["openapi.header.content-type"] = "application/json";

switch (reqpath) {
case "/samples/plain_greeter/v1/ping":
resp.Body = {"pong": count++};
$.sendResponse(s, resp, null);
break
case "/samples/plain_greeter/v1/echo":
resp.Body = {"echo": inbody};
$.sendResponse(s, resp, null);
break;
default:
$.sendResponse(s, null, Error("Unexpected operation at " + reqpath))
break;
}
}

The API function $.sendResponse() is used to send a response to the OpenAPI Servlow operator via the engine's response callback mechanism.

Please note that in SAP Data Hub 1.0 SPS03, the Javascript operator is the only way to use the response callback mechanism. In future releases of SAP Data Hub, there will be dedicated operators for invoking the response callback also outside of the Javascript operator. 

Interaction with the OpenAPI Servlow Operator


When running the pipeline, the service exposed by the OpenAPI operator can be reached via the following static URL:

https://<host>:<vsystem-port>/app/pipeline-modeler/openapi/service/<basePath>/

  • host: The hostname of the System Management service.

  • vsystem-port: The port of the System Management service.

  • basePath: The relative base path configured in the OpenAPI Servlow operator.


With that in place, you can test the OpenAPI Servlow based web service, e.g. using Postman:

GET-Request:


Postman example:



Corresponding Curl command:
curl --request GET \
--url https://<host>:<vsystem-port>/app/pipeline-modeler/openapi/service/samples/plain_greeter/v1/ping
\
--header 'cookie: Authorization="Bearer <token>"' \
--insecure

The authentication token can be obtained via following URL with basic authentication:
curl -vk -u "tenant\\user:password" https://<host>:<vsystem-port>/auth/login

POST-Request:


Postman example:



Corresponding Curl command:
curl --request POST \
--url https://<host>:<vsystem-port>/app/pipeline-modeler/openapi/service/samples/plain_greeter/v1/echo \
--header 'referer: https://<host>:<vsystem>' \
--header 'cookie: Authorization="Bearer <token>"' \
--header 'x-requested-with: XMLHttpRequest' \
--data 'testme' \
--insecure

The authentication token can be obtained via following URL with basic authentication:
curl -vk -u "tenant\\user:password" https://<host>:<vsystem-port>/auth/login

Once the POST-Request is sent to the server, you should see the following output in the Wiretap Operator which is then handled by the Javascript operator accordingly.



As you can see, it is very easy to handle various kinds of HTTP requests with the OpenAPI Servlow operator. The second pre-shipped example graph com.sap.demo.openapi.server.plain_greeter demonstrates an even more convenient method for implementing the server-side component by providing a Swagger specification in the operator configuration.

Custom Operator


Last but not least you can also embed your own RESTful service in an operator, e.g. using Flask in Python or Node.JS. Please stay tuned for an upcoming description.
15 Comments
yu_chen_10
Explorer
0 Kudos
Hi Jens,

many thanks for the good explanation.

I have found in SAP Data Hub another operator "Dashboard". As I read it's document, it's also a kind of reverse proxy. So what's the difference between "Dashboard" and the "Reverse Proxy", which you have used?

Best regards
Yu
jens_rannacher
Explorer
0 Kudos
Hi Yu,

there is no big difference, as both operators use internally the same mechanisms to realize the reverse proxy.

Best regards

Jens
0 Kudos
Jens,

Thanks for the explanation.

Could you please let me know where can I pass the required address in it Using the HTTP operator to work with GET method.

 

Thanks,
Anil
jens_rannacher
Explorer
0 Kudos
Hi Anil,

do you mean from where to derive the vsystem host and port or what address do you mean?

Best regards

Jens
former_member102219
Participant
0 Kudos
This is a very useful blog, thank you! I have a couple questions, however:

  1. There's no "Reverse Proxy" operator in my installation of Data Hub (Dev edition; the version is strangely shown as "NA", but it should be something late). I don't see that operator in the Repository Reference either.

  2. I used a hint from the comments here, and used Dahboard operator successfully. Still, the solution with picking the URL at runtime and using it in a client application looks more like a test/workaround than a possible productive solution. What is the recommendation on setting up a permanent URL for REST API?


Thanks!
0 Kudos
Hi Jens,

Can you please let us  know how do we get the info for the below parameter :

host: The hostname of the System Management service.
vsystem-port: The port of the System Management service.
basePath: The relative base path configured in the OpenAPI Servlow operator.

Best Regards

Shakti Kumar
jens_rannacher
Explorer
0 Kudos
Hi Shakati,

it is the host and port shown in the browser when working with the Data Hub Modeler. If you just see a hostname, e.g. when you have a load-balancer / ingress upfront, you just have to provide that hostname.

Best regards
Jens
jens_rannacher
Explorer
0 Kudos
Hi Roman,

in the meantime, there is a new HTTP Server 2 operator that makes this workaround unnecessary. I will have to update this blog accordingly once I find time.

Also we are planning an API Manager that will improve the managing/handling of APIs in general.

Best regards
Jens
martin_donadio
Employee
Employee
0 Kudos
Hi Jens,

 

Great blog post !

 

I am using DI Version 2003.1.15.

 

When I try the login command using curl I get "invalid credentials", but I am sure the credentials are Ok, as I am able to login in the home page.

 

Do you know if the login API changed?

 

Thanks

 

Martin
rob_innes1
Discoverer
0 Kudos
Just finished struggling with the same issue.

Solution is: *single* backslash between tenant and user when requesting the token.  Not double.

 
curl -vk -u "tenant\user:password" https://<host>:<vsystem-port>/auth/login
0 Kudos
Hi Jens, thanks for this very nice post!

Just one question regarding OpenAPI Servlow operator. I was managed to create working API endpoint, it's working well on my user, but other users are not able to get to this endpoint (DataHub returns "illegal URL request path /openapi/service/samples/plain_greeter/v1/ping. No executing host found".

Both users are logging in on the same machine, same browser...
Hi, Michał.

I have the same issue. I think this is "not bug but feature" of DI. DI-user executes and sends http-get-requests to only his/her owned graphs. Indirect confirmation of it is the DI Monitoring app. In the app you can schedule only graphs from your workspace, only these graphs are available in dropdown list.

Hence, in SAP DI, if you need several end-users to execute same graph, you have 2 options:

  1. Create separate DI-user, import graph to its workspace and give the DI-user's credentials (login/password) to all end-users who need to execute the graph.

  2. Create corresponding DI-user for the every end-user. Import to their workspaces the graph. Ask all of these new DI-users to start their graphs.


I also found that 2 different DI-users may start their graphs even if their "OpenAPI Servlow" operators have identical base path. In other words, user1 starts graph1 and user2 starts graph2. Graph1 has end-point: https://<server>.hana.ondemand.com/app/pipeline-modeler/openapi/service/samples/plain_greeter/v1/pin...

And graph2 has exactly same endpoint. Despite this both graphs will work correctly and indendently on each other. Graph1 will be available only for user1, and graph2 - only for user2.

 
michal_majer
Active Participant
0 Kudos
Hey jens.rannacher

Thanks for perfect blog! I would like to implement custom operator (Node/Python) which will expose HTTP server (for example using express.js).

I successfully can launch it, but I couldn't communicate with that server. I guess what I need is a reverse proxy between hosted pipe and localhost server.

Do you know how I could achieve that?
How I could run own RESTful service in an operator?
How it was implement in HTTP Server, OpenAPI Servlow?

Best,
Michal
rajeshps
Participant
0 Kudos
 

sergey.shablykin
martin.donadio

majer.michal

jens.rannacher

studentsap

shakti.kumar3

 

Could you please check the below question and provide your valuable inputs. Thankyou!

https://answers.sap.com/questions/13795123/call-abap-proxy-from-sap-di.html
sanju5454
Explorer
0 Kudos
How to enable cors in sap di servlow operator ?

How to set response headers for preflight request  in servlow operator?

operator need to detect the preflight request and need to send these headers "Access-Control-Allow-Origin": "*", "Access-Control-Allow-Methods" : "*" for preflight request in servlow operator.




when preflight request is hitting the given url with get method, It is hitting options method instead of get method. and thst method are not even showing in wiretap. To find this error I setup a local public server then i found that it's sending the options request as preflight request instead of get or post.


So I need to set a middleware or some other similar setup. To fix this error.


Currently cors are not enabled for the servlow operator. I'm looking for a solution to fix this cors error.