Technology Blogs by Members
Explore a vibrant mix of technical expertise, industry insights, and tech buzz in member blogs covering SAP products, technology, and events. Get in the mix!
cancel
Showing results for 
Search instead for 
Did you mean: 
fabiocerioni
Explorer
1,901

Introduction


I am actually involved in a project in which I have to integrate AppGyver with a SAP ERP. The integration must work in mobile apps and includes update operations.
SAP is actually releasing a BTP integration with AppGyver (https://blogs.sap.com/2022/07/01/using-btp-authentication-and-destinations-with-sap-appgyver/) which looks very interesting.

Unfortunately at the date in which I am writing this blog, the solution is not working yet on mobile apps, therefore I’ve decided to take an alternative path and integrate AppGyver and SAP through REST JSON rather than OData.

The solution involves very few components works pretty well, is simple, easy to program (for the ABAP old guys), gives you full control, resolves all the CORS issues on GET and…. very important on PUT and POST operations.

Works on web and mobile App without any problem.

Solution Overview


The development environment that I’ve used is the following


General Architecture


SAP backend exposes a JSON interface via the Internet Communication Framework (ICF)=.

Approuter BTP component publish the ABAP JSON service to AppGyver.

As alternative to approuter for development purpose you can use also NGROK (https://ngrok.com/)

The use case that I’ve implemented consists in listing of assets of the ERP and eventually modify one of their fields (e.g inventory number). You can adapt easily to any other use case.

Defining the JSON Service


I’ve create a simple Rest service in ICF. Refer to this blog in order to see how.

Handling class that implements service is called ZASSET_HTTP

I’ve defined three additional methods apart the standard HANDLE_REQUEST coming from interface IF_HTTP_REQUEST


 

HANDLE_REQUEST method code is the following:
  METHOD IF_HTTP_EXTENSION~HANDLE_REQUEST.
DATA: it_header_fields TYPE tihttpnvp.
DATA: it_form_fields TYPE tihttpnvp.

CALL METHOD server->request->if_http_entity~get_header_fields
CHANGING
fields = it_header_fields.

CALL METHOD server->request->if_http_entity~get_form_fields
CHANGING
fields = it_form_fields.

DATA: wa_method LIKE LINE OF it_header_fields.
DATA: wa_action LIKE LINE OF it_header_fields.

CREATE OBJECT ASSET_BACKEND.
READ TABLE it_header_fields INTO wa_method WITH KEY name = '~request_method'.
READ TABLE it_form_fields INTO wa_action WITH KEY name = 'action'.

IF ( wa_method-value = 'GET' and wa_action-value = 'getlist' ).
CALL METHOD getlist
EXPORTING
server = server
.
elseIF ( wa_method-value = 'GET' and wa_action-value = 'getdetail' ).
CALL METHOD getdetail
EXPORTING
server = server
.
ELSEIF ( wa_method-value = 'PUT' ) or ( wa_method-value = 'OPTIONS' ).
CALL METHOD update
EXPORTING
server = server.
ENDIF.
ENDMETHOD.

GETLIST method code:
method GETLIST.
DATA: it_header_fields TYPE tihttpnvp.
DATA: it_form_fields TYPE tihttpnvp.
FIELD-SYMBOLS: <form> TYPE ihttpnvp.
DATA: wa_origin TYPE ihttpnvp.
DATA: lv_result TYPE string.
DATA: cdata TYPE string.
DATA: lv_bukrs TYPE bukrs.

CALL METHOD server->request->if_http_entity~get_form_fields
CHANGING
fields = it_form_fields.
CALL METHOD server->request->if_http_entity~get_header_fields
CHANGING
fields = it_header_fields.
READ TABLE it_header_fields INTO wa_origin WITH KEY name = 'origin'.
READ TABLE it_form_fields ASSIGNING <form> WITH KEY name = 'bukrs'.
IF ( sy-subrc = 0 ).
lv_bukrs = <form>-value.
endif.
CALL METHOD ASSET_BACKEND->GETLIST
EXPORTING
i_bukrs = lv_bukrs
IMPORTING
result = lv_result.

server->response->set_header_field(
name = 'Content-Type' "#EC NOTEXT
value = 'application/json' ).
if ( wa_origin-value = 'https://appgyver-ompmyzem-platform.appgyver.black' or
wa_origin-value = 'https://appgyver-ompmyzem.preview-btp.appgyver.black' or
wa_origin-value = 'https://appgyver-ompmyzem.preview.appgyver.black' or
wa_origin-value = 'https://appgyver-ompmyzem.appgyver.black' ).
******** enable CORS Access
server->response->set_header_field(
name = 'Access-Control-Allow-Origin'
value = wa_origin-value ).
endif.
server->response->set_cdata( data = lv_result ).
server->response->set_status( code = 200 reason = 'OK' ).
endmethod.

If you want to transform an ABAP list/structure to JSON use the class  /ui2/cl_json. If you don’t have this class on your system look in the SAP Community. There are plenty of alternative solutions.

Notice the code at the end to handle CORS requests.

If the origin of the request comes from Appgyver platform I set the 'Access-Control-Allow-Origin' header value to the value of the origin header.


Get Request exchange


In order to understand CORS I’ve used this article (https://developer.mozilla.org/en-US/docs/Web/HTTP/CORS)

I won’t show the GETDETAIL code that is quite similar conceptually to the GETLIST

The UPDATE code is the following:
 METHOD update.

DATA: it_header_fields TYPE tihttpnvp.
DATA: wa_method TYPE ihttpnvp.
DATA: body TYPE string.
DATA: wa_asset TYPE zinm_activo_ext.
DATA: it_form_fields TYPE tihttpnvp.
FIELD-SYMBOLS: <form> TYPE ihttpnvp.
FIELD-SYMBOLS: <header> TYPE ihttpnvp.
FIELD-SYMBOLS <origin> TYPE ihttpnvp.
DATA: lv_response TYPE bapiret2.
DATA: ret_json TYPE string.
DATA: cdata TYPE string.
DATA: wa_invnr TYPE zinm_activo_ext.
DATA: wa_anln1 TYPE anln1.
DATA: wa_anln2 TYPE anln2.
DATA: wa_bukrs TYPE bukrs.

CALL METHOD server->request->if_http_entity~get_header_fields
CHANGING
fields = it_header_fields.
CALL METHOD server->request->if_http_entity~get_form_fields
CHANGING
fields = it_form_fields.


READ TABLE it_header_fields INTO wa_method WITH KEY name = '~request_method'.
IF wa_method-value = 'OPTIONS'.
READ TABLE it_header_fields ASSIGNING <origin> WITH KEY name = 'origin'.
IF sy-subrc = 0.
IF ( <origin>-value = 'https://appgyver-ompmyzem-platform.appgyver.black' OR
<origin>-value = 'https://appgyver-ompmyzem.preview-btp.appgyver.black' OR
<origin>-value = 'https://appgyver-ompmyzem.preview.appgyver.black' OR
<origin>-value = 'https://appgyver-ompmyzem.appgyver.black' ).
******** enable CORS Access
server->response->set_header_field(
name = 'Access-Control-Allow-Origin'
value = <origin>-value ).
ENDIF.
server->response->set_header_field(
name = 'Access-control-allow-methods'
value = 'PUT, POST, GET, OPTIONS' ).
server->response->set_header_field(
name = 'Access-Control-Allow-Headers'
value = 'X-PINGOTHER, Content-type' ).
server->response->set_header_field(
name = 'Access-Control-Max-Age'
value = '86400' ).


ret_json = /ui2/cl_json=>serialize( data = lv_response compress = abap_true pretty_name = /ui2/cl_json=>pretty_mode-camel_case ).
server->response->set_cdata( data = ret_json ).
server->response->set_status( code = 204 reason = 'No Content' ).
RETURN.
ENDIF.
ELSEIF wa_method-value = 'PUT'.
CALL METHOD server->request->if_http_entity~get_cdata
RECEIVING
data = body.
CALL METHOD /ui2/cl_json=>deserialize
EXPORTING
json = body
CHANGING
data = wa_asset.

READ TABLE it_form_fields ASSIGNING <form> WITH KEY name = 'anln1'.
IF ( sy-subrc = 0 ).
wa_anln1 = <form>-value.
ENDIF.
READ TABLE it_form_fields ASSIGNING <form> WITH KEY name = 'anln2'.
IF ( sy-subrc = 0 ).
wa_anln2 = <form>-value.
ENDIF.
READ TABLE it_form_fields ASSIGNING <form> WITH KEY name = 'bukrs'.
IF ( sy-subrc = 0 ).
wa_bukrs = <form>-value.
ENDIF.

READ TABLE it_header_fields ASSIGNING <origin> WITH KEY name = 'origin'.
IF sy-subrc = 0.
IF ( <origin>-value = 'https://appgyver-ompmyzem-platform.appgyver.black' OR
<origin>-value = 'https://appgyver-ompmyzem.preview-btp.appgyver.black' OR
<origin>-value = 'https://appgyver-ompmyzem.preview.appgyver.black' OR
<origin>-value = 'https://appgyver-ompmyzem.appgyver.black' ).
******** enable CORS Access
server->response->set_header_field(
name = 'Access-Control-Allow-Origin'
value = <origin>-value ).
ENDIF.
ENDIF.
CALL METHOD asset_backend->update
EXPORTING
i_bukrs = wa_bukrs
i_anln1 = wa_anln1
i_anln2 = wa_anln2
i_invnr = wa_asset-invnr
i_anexo = wa_asset-anexo
i_extension = wa_asset-extension
IMPORTING
e_result = lv_response.
IF ( <origin> IS ASSIGNED ).
server->response->set_header_field(
name = 'access-control-allow-origin'
value = <origin>-value ).
ENDIF.
server->response->set_header_field(
name = 'content-type'
value = 'application/json' ).

ret_json = /ui2/cl_json=>serialize( data = lv_response compress = abap_true pretty_name = /ui2/cl_json=>pretty_mode-camel_case ).
server->response->set_cdata( data = ret_json ).
server->response->set_status( code = 200 reason = 'OK' ).
ENDIF.
ENDMETHOD.

 

In this case AppGyver sends two kind of requests

-‘OPTIONS’ to know if can perform a PUT operation: In this case, the response code must be ‘204’ without any body.


Options request


-‘PUT’ to carry out the update. In this case response code must be a ‘200’ with the body in JSON format.


PUT Request


 

.

 

Code can be modularized much better but to make it simpler I’ve left everything in one method.

Defining the approuter


Through the approuter it willl be possible to publish our service to BTP to make it available to AppGyver.

Follow this blog to configure the cloud connector

And follow this video in order to define the BTP approuter component

The xs-app.json file that you have to configure should look like this.


xs-app.json


Where you have to put your BTP destination.

authenticationMethod is set to “none” just to simpifly the scenario.

In production scenario you should configure an appropiate authentication method

Testing the approuter


Once you deployed the MTA you should get from the BAS terminal the URL published


Test the URL published adding the arguments for the service call. You should get the JSON answer.


 

Testing from AppGyver.


From the AppGyver click the data Icon and choose from AppGyver classic Data Entities ⇒ Create Data Entity ⇒ Rest API Direct Integration


Define the service using the public URL given by approuter.


In the getCollection I’ve added the action parameter set to the static value “getlist” and the BUKRS parameter

If I test I’ve got the result.


And finally update case


If I test it....


works pretty well.

Conclusion


Until the release of the AppGyver in which the BTP integration will be available on mobile, this kind of integration can be used to make communicate AppGyver with an SAP ERP or S/4 HANA on mobile app supporting the update operation.

This solution does not need SAP Gateway, therefore it is interesting as well for landscapes that does not have this component yet.

This integration can be considered as well as a useful exercise to understand the CORS protocol and how AppGyver requests works.

If you are interested on more blogs con AppGyver you can follow this link

I hope you found this blog useful. If you have any questions or, let me know in the comment.
4 Comments
Labels in this area