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
Advisor
Advisor
7,221
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