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: 
boudhayan-dev
Advisor
Advisor
13,360
The best way to learn is by doing. The only way to build a strong work ethic is getting your hands dirty.

                                                                                                                - Alex Spanos



In this blog I'll demonstrate how to connect services that are deployed on an on-premise system to a Flask application that is deployed on Cloud Foundry. For this exercise, we will make use of the SAP Cloud Connector.

While working on a project that required me to connect CF and On-Premise systems, I came across two very wonderful blogs written by -  Matthieu Pelatan and  Marius Obert . While Matthieu's blog shows end to end connection between the two systems, he relies on JAVA to demonstrate his example. Since I am not very familiar with JAVA, I decided to follow - Marius article. Marius shows how to call destinations endpoints from Flask application, which is great but it did not solve my cross-system connectivity issue. So, I decided to write this blog to show how we can call services from different systems (on-prem) via Flask (Python) application in CF environment. I thank mariusobert#overview and matthieu.pelatan#overview for providing such wonderful resources.



1. On-Premise


For the purpose of this blog, we will have a service (XSJS) deployed in an on-premise system as follows -

process_query.xsjs -
// Function that return current balance ( hard coded )
function returnBalance(){
try{
var output = {
status:200,
replies:[{
'type': 'text',
'content': 'Hi, current balance is: $ 23,900'
}]
};

var body = JSON.stringify(output);
$.response.contentType = 'application/json';
$.response.setBody(body);
$.response.status = $.net.http.OK;
}

catch (e) {
var output2 = {
status:500,
replies:[{
'type': 'text',
'content': e.message
}]

};

$.response.contentType = 'application/json';
var body2 = JSON.stringify(output2);
$.response.setBody(body2);
$.response.status = $.net.http.OK;

}

}

returnBalance();

 

Service status - 



 

The service is available now as seen above. Now we shall expose this service via SAP Cloud connector.

 

2. SAP Cloud Connector


 

Our SAP CF account looks as follows -



 

Now, let's make the Connection between our CF system and On-premise system using Cloud Connector -

 

2.1. Create connection to CF



2.2. Add the On-Premise system










 

2.3. Add the service endpoint.




 

3. Cloud Foundry


Now that we have set up connection between the Cloud Foundry environment and the On-premise system, let's deploy our Flask application.

3.1 Create Destination


Let's add a new Destination entry which will point to the <VirtualHost>:<VirtualPort> corresponding to the <ActualHost>:<ActualPort> that we have mapped in SAP Cloud connector.



 

3.2 Add Destination, XSUAA and Connectivity service.


Lets's add the Destination , XSUAA and Connectivity service to out CF Sub-account.The purpose of these services are as follows -

  • Destination - Provides a uniform way to add service endpoints in an application.

  • XSUAA - Provides authentication service.

  • Connectivity - This will invoke the Cloud Connector service which in turn will call our services deployed in On-premise system.


3.2.1 Service



 

For the services - Just provide the plan and the name of the service. DO NOT provide anything in the Specify Parameters/ Assign Application section.

After adding all the required services, Navigate to service instance tab -


3.3 Flask Application


 

Let's add our Flask Application now

app.py -
from flask import Flask, request, jsonify
import requests
import json
import base64
from cfenv import AppEnv


app = Flask(__name__)
env = AppEnv()

# Creating instances of the Destiantion, XSUAA and Connectivity service
UAA_SERVICE = env.get_service(name='uaa_service')
DESTINATION_SERVICE = env.get_service(name='destination_service')
CONNECTIVITY_SERVICE = env.get_service(name='connectivity_service')
# Destination name
DESTINATION = 'demoService'
#connectivity proxy
CONNECTIVITY_PROXY = CONNECTIVITY_SERVICE.credentials["onpremise_proxy_host"]+":"
+ CONNECTIVITY_SERVICE.credentials["onpremise_proxy_port"]

# Connectivity and Destination Authentication tokens
CONNECTIVITY_SECRET = CONNECTIVITY_SERVICE.credentials["clientid"] + \
':' + CONNECTIVITY_SERVICE.credentials["clientsecret"]

DESTINATION_SECRET = DESTINATION_SERVICE.credentials["clientid"] + \
':' + DESTINATION_SERVICE.credentials["clientsecret"]

CONNECTIVITY_CREDENTIALS = base64.b64encode(
CONNECTIVITY_SECRET.encode()).decode('ascii')

DESTINATION_CREDENTIALS = base64.b64encode(
DESTINATION_SECRET.encode()).decode('ascii')


def getAccessToken(credentials, serviceName):
#Getting access token for Connectivity service.
headers = {'Authorization': 'Basic ' + credentials,
'content-type': 'application/x-www-form-urlencoded'}

form = [('client_id', serviceName.credentials["clientid"]),
('grant_type', 'client_credentials')]

r = requests.post(
UAA_SERVICE.credentials["url"] + '/oauth/token', data=form, headers=headers)

token = r.json()['access_token']
return token


# Helper that Returns the URL of the destination.
def _getDestinationURL(token):
headers = {'Authorization': 'Bearer ' + token}

r = requests.get(DESTINATION_SERVICE.credentials["uri"] +
'/destination-configuration/v1/destinations/' + DESTINATION, headers=headers)

destination = r.json()
return destination["destinationConfiguration"]["URL"]


def getURL():
# Fetch URL of the Destination
destination_token = getAccessToken(
DESTINATION_CREDENTIALS, DESTINATION_SERVICE)
url = _getDestinationURL(destination_token)
return url


def getProxy():
data = {}
connectivity_token = getAccessToken(
CONNECTIVITY_CREDENTIALS, CONNECTIVITY_SERVICE)

# Setting proxies and header for the Destination that needs to be called.
headers = {
'Proxy-Authorization': 'Bearer ' + connectivity_token}
# connection['headers'] = str(headers)
# proxy
proxies = {
"http": CONNECTIVITY_PROXY
}
# connection['proxies'] = str(proxies)
data['headers'] = headers
data['proxies'] = proxies
return data

def makeRequest(request,endpoint):
# Get destination URL
url = getURL()
#Get proxy parameters
connection = getProxy()

# Call the on-prem process_query service.
r = requests.post(url+endpoint,
proxies=connection['proxies'], headers=connection['headers'],
verify=False, timeout=10)
return json.loads(r.text)


# Routes
@app.route('/process_query', methods=['POST', 'GET'])
def process_query():
responseText = makeRequest(request,'/process_query.xsjs')
return jsonify(responseText)



if __name__ == '__main__':
app.run(host='0.0.0.0', port=8080, debug= True)

 

Let's break down the code and see what's going on -

  • We have created an endpoint /process_query which shall call the process_query.xsjs service in On-Premise system.

  • getAccessToken() - This function provides a JWT token from the XSUAA service. For the connectivity and destination service, we generate two different JWT Token.

  • getURL() - This function invokes the destination service and fetches the Endpoint defined under the demoService destination.

  • getProxy() -  This function provides a proxy for the connectivity service to call the SAP Cloud Connector and in turn invoke the on-premise service.


 

We shall bind all the 3 services - Destination, Connectivity and XSUAA service in the manifest.yml  file -
---
applications:
- memory: 128MB
name: demo_app
disk_quota: 256MB
random-route: true
services:
- destination_service
- uaa_service
- connectivity_service

 

We shall freeze the Flask application's dependencies in a requirements.txt  file -
Flask
requests
cfenv

 

We shall also add a Procfle  - 
web: python app.py

Finally, we will add the command needed to start the application in a  runtime.txt  as follows -
python-3.6.6

Our application directory will be as follows -



Now that we are ready with our application, let's deploy it-
cf push demo_app -u none

Deployment logs -



 

 

4. Test


 

Let's test our application and see what we get -

Hit the URL- demoapp-silly-buffalo.cfapps.sap.hana.ondemand.com/process_query



SUCCESS !!!

 

Now, our application deployed in Cloud Foundry is able to communicate with XSJS services deployed in an On-Premise system.

I hope this helps anyone who wants to connect systems using SAP Cloud Connector.

 

Regards,

Boudhayan Dev

 

 

EDIT -

The sample On-Premise service shown above does not require any authentication. However, it is very common to have some sort of authentication for your backend services. If that is the case, please use the following modified code -

Calling On-premise service with authentication

 

Do not forget to update the credentials in destination tab before this.
11 Comments