Application Development Blog Posts
Learn and share on deeper, cross technology development topics such as integration and connectivity, automation, cloud extensibility, developing at scale, and security.
cancel
Showing results for 
Search instead for 
Did you mean: 
vladimir_erakovic
Contributor

Don’t get me wrong, I really like SAP Gateway and oData but sometimes it is not the most simple and elegant solution. Imagine that you have this kind of scenario: On corporate network in VPN, there is a separate server with .NET application on it and you have to create services for communication with that application in both direction, from ABAP application to call services in .NET application and in ABAP application to have services that will be called from .NET application (client and server scenario). Requirements are that services requests and responses are in JSON format with flat or deep structures and to use GET, POST and PUT methods (for reading, creating and updating data). And last but no less important fact is that your company SAP servers doesn’t all have Gateway but there is only one separate Gateway server that is connected to all three SAP servers (DEV, QAS, PRD). So basically, you should create RFCs for oData services and somehow manipulate complex structures and POST/PUT http methods and JSON requests and responses, ugh!

But, fortunately there is ICF (Internet Communication Framework) in ABAP and some new very good class for JSON serialization/deserialization (/UI2/CL_JSON) and with some know-how and little imagination you could create all services from requirements with only two classes and one service in SICF. For some more information about ICF please read:

http://help.sap.com/abapdocu_750/en/index.htm?file=abenicf.htm

http://help.sap.com/saphelp_nw74/helpdata/en/48/c7c2d2da5e31ebe10000000a42189b/content.htm

So, we will first create a class for ABAP client, to call services in .NET application.

Go to SE24 or SE80, whatever you prefer, and create a new class. Name it ZREST_CLIENT.

Because we are developing a class that should cover all services from some business scenario, we will create separate methods: one for selecting and preparing data, one for each service that need to be called and one for sending requests. All methods are static so there is no need for creating an object of a class and also public except the method for sending request. Here, on images, there is only an example of class for sending request to two services, first with GET method to get a list of materials and second with POST method for sending a list of quantities for those materials. Service call have a request and response but with GET method a request data is connected to service URL while with POST method a request is in the body of HTTP request.

I will not go into details with data structures and data selection but I will explain the main idea and mechanism how this class works.

In your program or wherever you want to call these services, just make a call to SELECT_DATA method which have parameters: Import parameters are all values that will be needed for service call and request type or method/service that you calling and Export parameter that could at least be HTTP status code.

In example on image import parameters are factory and year. On other images are parameters for every method but without data types. It is enough to said that for service call with JSON in service request, import parameter is of ABAP structure type while URL parameter for GET method service calls and service responses are of type String. When working with JSON there is often need for deep structures but I will not show it here for simplicity. If you have any questions feel free to ask.

And now the code for methods.


METHOD select_data.

*     VARIABLE DECLARATION
DATA: ls_quantities TYPE z_quantities.

CASE im_request_type.
WHEN 1. " SEND_QUANTITIES

*     GET DATA
SELECT

ENDSELECT.


*     CALLING METHOD
send_quantities
(
EXPORTING it_materials_quantities = ls_quantities
IMPORTING status_code = ex_status_code ).



WHEN 2. " GET_MATERIALS

DATA: lv_uri_param TYPE string,
lv_response
TYPE string.
DATA: lt_materials TYPE TABLE OF z_materials.

CONCATENATE '?factory=' im_factory INTO lv_uri_param.

get_materials
(
EXPORTING uri_parameters = lv_uri_param
IMPORTING status_code = ex_status_code
response_data
= lv_response ).

* deserialize JSON string json into internal table lt_flight doing camelCase to ABAP like field name mapping
/ui2/cl_json
=>deserialize( EXPORTING json = lv_response
pretty_name
= /ui2/cl_json=>pretty_mode-camel_case
CHANGING  data = lt_materials ).


" SAVE INTERNAL TABLE TO DATABASE

WHEN OTHERS.
ENDCASE.

ENDMETHOD.

METHOD get_materials.

DATA: lv_json TYPE string,
request
TYPE z_request.

IF uri_parameters IS NOT INITIAL.

lv_json
= ''.
request
= '2'.

send_request
(
EXPORTING
json_string 
= lv_json
request_type
= request
uri_parameters
= uri_parameters
IMPORTING
status_code 
= status_code
response_data
= response_data ).

ELSE.
RAISE empty_parameters.
ENDIF.

ENDMETHOD.

METHOD send_quantities.

DATA: lv_json TYPE string,
request
TYPE z_request.

IF it_materials_quantities IS NOT INITIAL.
*   serialize structure/table into JSON, skipping initial fields and converting ABAP field names into camelCase
lv_json
= /ui2/cl_json=>serialize( data = it_materials_quantities

compress = abap_false
pretty_name
= /ui2/cl_json=>pretty_mode-camel_case ).

request
= '1'.

send_request
(
EXPORTING
json_string 
= lv_json
request_type
= request
IMPORTING
status_code 
= status_code ).

ELSE.
RAISE empty_structure.
ENDIF.

  1. ENDMETHOD.

METHOD send_request.

DATA: http_client TYPE REF TO if_http_client .
DATA: lv_len TYPE i,
lv_result
TYPE string,
http_status_code
TYPE i,
status_text
TYPE string.

DATA: url TYPE string,
http_method
TYPE string.

IF json_string IS NOT INITIAL OR uri_parameters IS NOT INITIAL.

*  set service url addresses (dummy in this case..)

IF request_type = '1'.
url
= 'http://100.100.10.100/api/sap/quantities'.
http_method
= 'POST'.
ELSEIF request_type = '2'.
url
= 'http:// 100.100.10.100/api/sap/materials'.
CONCATENATE url uri_parameters INTO url.
http_method
= 'GET'.
ENDIF.


cl_http_client
=>create_by_url(
EXPORTING
url               
= url
IMPORTING
client             = http_client
EXCEPTIONS
argument_not_found
= 1
plugin_not_active 
= 2
internal_error    
= 3
OTHERS             = 4 ).

*  set protocol version
http_client
->request->set_header_field(
EXPORTING
name 
= '~server_protocol'
value = 'HTTP/1.1' ).

*  content type
http_client
->request->if_http_entity~set_content_type(
EXPORTING
content_type
= 'application/json; charset=utf-8' ).

http_client
->request->set_header_field(
name 
= '~request_method'
value = http_method ).

http_client
->request->set_header_field(
EXPORTING
name 
= 'Content-Type'
value = 'application/json; charset=utf-8' ).

IF request_type <> '2'*  not need for GET method

lv_len
= STRLEN( json_string ).

http_client
->request->set_cdata(
EXPORTING
data   = json_string
offset
= 0
length
= lv_len ).

ENDIF.

http_client
->send(
EXCEPTIONS
http_communication_failure
= 1
http_invalid_state        
= 2 ).

*  Receive the Response Object
http_client
->receive(
EXCEPTIONS
http_communication_failure
= 1
http_invalid_state        
= 2
http_processing_failed    
= 3 ).

IF request_type = '2'. *  Needed for GET method, but POST also may have response data..

CLEAR lv_result .
lv_result
= http_client->response->get_cdata( ).
response_data
= lv_result.

ENDIF.

*  Read HTTP RETURN CODE
http_client
->response->get_status(
IMPORTING
code = http_status_code
reason
= status_text ).

status_code
= http_status_code.

ENDIF.

ENDMETHOD.

And that is the simplest example of ABAP REST client class. PUT and DELETE method were not showed here but it’s easy to implement them.

In the part 2 of this document, we will develop ABAP REST server class for handling service requests from outside of ABAP AS.