In this example, we implement an 'echo' webservice, which takes a string 'STRING' and an additional parameter 'REVERSE' to reverse the argument. The result is passed in the parameter 'RESULT'.
FUNCTION Z_EPO_JSON_STRING .
*"----------------------------------------------------------------------
*"*"Local Interface:
*" IMPORTING
*" REFERENCE(STRING) TYPE STRING
*" REFERENCE(REVERSE) TYPE FLAG
*" EXPORTING
*" REFERENCE(RESULT) TYPE STRING
*"----------------------------------------------------------------------
" string - webservice:
" return the string;
" if 'reverse' is not an empty string, reverse the string
DATA:
lv_reverse TYPE flag,
lv_char TYPE char1024.
" conversion to character in order to handle an string with spaces
lv_reverse = reverse.
IF lv_reverse IS INITIAL.
" simply copy:
result = string.
ELSE.
" reverse that string
" note: STRING_REVERSE does not handle strings..
lv_char = string.
CALL FUNCTION 'STRING_REVERSE'
EXPORTING
string = lv_char
lang = sy-langu
IMPORTING
RSTRING = lv_char
EXCEPTIONS
OTHERS = 0 " ignore errors
.
result = lv_char.
ENDIF.
ENDFUNCTION.
For a clean interface, there are only importing and exporting parameters. We do not use: changing- / tables parameter and do not raise exceptions. The error handling should supply readable error messages into exporting parameter(s).
Note: usually, the importing/changing/tables/exporting parameters are mapped to separate JSON sub-objects. If there are only importing and exporting parameters, we can use a transformation in order to receive only importing parameters and to return only exporting parameters (see 'field mapping').
The import parameters 'STRING' and 'REVERSE' should be posted in such a JSON structure:
{
"string":"test",
"reverse":"X"
}
The result should be returned in such a JSON structure:
{
"result":"tset"
}
In SICF, it is required to activate an element, which uses the EPO handler: activate the 'epo1soa' / 'jsonhandler'
..which uses the /EPO1/JSONHANDLER handler:
Logon data: supply the SAP-client and the language (otherwise, it would be necessary to specify the SAP-client as URL parameter). If there are multiple SAP-clients to maintain, it would be possible to define a separate SICF element for each client - or to specify the sap client as URL parameter (e.g. '?sap-client=100&sap-language=DE').
With the definition of a service / operation, the EPO framework will be enabled to make a function module accessible as a webservice.
Start the transaction /EPO1/EXC, which is an area menu
If not exists, define two number range. For EPO services, we use the number range '00' for incoming services and '01' for outgoing services
Transaction /EPO1/EXC
Create the intervals '00' and '01'':
Transaction /EPO1/EXC
Create a new service 'EPOFMJSON_STRING_REVERSE':
Important fields:
When using GFMC, the operation is mapped to the function module. Otherwise, the operation could also be mapped to another function module, using the menu entry GFMC: Change processing FM for given operation.
Transaction /EPO1/EXC
Create a new operation 'Z_EPO_JSON_STRING'
Important fields:
We want now to create the API path, which is used to invoke the service.
http or https, servername, port (if not the default port '80' for http, '443' for https)
Together we get something like
http://my.server.com:80/epo1soa/jsonhandler/EPOFMJSON_STRING_REVERSE/Z_EPO_JSON_STRING
All the names of the JSON request / response has to be mapped to ABAP fields. To make the life easier, we will use the table /EPO1/FIELD_MAP in order to maintain the mapping. In our case, the field names are very simple, so a 'lowercase' format would be sufficient.
For this example, we will simply use the [lowercase] - mapping: every ABAP letter will be mapped to a lower-case JSON letter.
Note: when using the [camelCase] or [CamelCase] - mapping for inbound, use the camelCase - IMPORT - transformation /EPO1/EXC_JSON_IMPORT_STRUC_CC instead of /EPO1/EXC_JSON_IMPORT_STRUC. Otherwise, the 'IMPORT' - field name will be mapped to '_I_M_P_O_R_T' or 'I_M_P_O_R_T', which will not work.
Alternatively, You might map the INPUT field explicitely (possible, but not recommended):
Sometimes, it is more convenient to have the mapping information directly in code instead of relying on customiziation.
The contents of the field name mapping - /EPO1/FIELD_MAP can also be defined in code, but the conversion ABAP <-> JSON has to be called manually (using the methods /EPO1/CL_TOOLS=>ABAP_TO_JSON or /EPO1/CL_TOOLS=>JSON_TO_ABAP).
Sometimes, an universal webservice will provide several completely different data structures (e.g. user data, invoice data, material data, ..).
This can be done by defining a large structure, which can hold each of the data to be delivered. However, the 'other' structure elements should not be returned.
Or, some 'initial' fields should be removed from the response (e.g. date fields, character strings, ..).
There are 2 implementations, wich are controlled by the response format in the service configuration table /EPO1/CONFIGOUT:
response format: 't', 'tb' or 'tbi'
response format: '0', '1', '2' or '10'
Please note, that the output of those both methods differ in some details (mapping of field names with '/', number- or date formatting).
Requirement: define one of the response formats: 't', 'tb' or 'tbi' Please note, that the stripping of output structures is limited to structures (und substructures); the inner content of a table cannot be stripped.
Define the export parameter ES_EPO1_ABAP_STRIP with the type /EPO1/EXC_ABAP_STRIP_STRUC. This structure contains all necessary parameters, which are used in the method /epo1/cl_tools=>abap_strip.
Meaning of the ABAP-STRIP - parameters:
Note 1: elements and sub-elements are to be written as in ABAP (e.g. ELEMENT-SUB_ELEMENT-SUB_SUB_ELEMENT-...)
Note 2: the upper most element is the generated element 'EXPORT' for export parameters, 'CHANGING' for changing parameters and 'TABLES' for tables parameters. For simplicity, we expect, that only export parameters are used in a webservice - function module.
Note 3: it is not possible to change the contents of a table; only structure element might be stripped.
Example - export parameters of the function module for the webservice:
USER_DATA TYPE USR01
MATERIAL_DATA TYPE MARA
MESSAGES TYPE BAPIRET2_T
remove everything except the user data:
es_epo1_abap_strip-iv_strip_everything = 'X'.
APPEND 'EXPORT-USER_DATA' TO es_epo1_abap_strip-it_keep.
Note: usually, we know exactly, which data are to be returned, so this is the preferred way to strip output data. This will work even if the webservice is enhanced in the future by returning additional data.
remove all empty structures from export:
es_epo1_abap_strip-iv_strip_initial = 'X'.
Note: this is maybe the most universal setting; leaving only structure elements, which has been filled
remove every empty element (recursive):
es_epo1_abap_strip-iv_strip_initial = 'X'.
es_epo1_abap_strip-iv_strip_recursive = 'X'.
Note: this stripping might be useful, when very large structures are returned, where most of the elements are initial and does not need to be returned. So we can remove all of the unused data. If there are elements, which should be returned, even when initial - then simply add the names of these elements to the IT_KEEP list.
remove only the material data (complete structure):
APPEND 'EXPORT-MATERIAL_DATA' TO es_epo1_strip-it_strip.
remove only the material number from the material data (maybe useless.. just so show, how it works):
APPEND 'EXPORT-MATERIAL_DATA-MATNR' TO es_epo1_strip-it_strip.
Requirements:
For details, please refer the section /EPO1/JSON_STRIP
As seen before, the API path contains the EPO service (which could be defined with a lowercase name) and the operation, which is always to be written in uppercase.
The easy part is the usage of another EPO service: go back to the configuration of the EPO service, create a lowercase/camelcase - servicename (e.g. stringReverse) and a new operation.
The next step is to map the new service/operation to Your function module:
Transaction /EPO1/EXC: navigate to the item 'GFMC: Change processing FM for given operation'
and create a new entry with Your new service name, choose a new (lowercase/camelcase) operation name and Your existing function module name, e.g.:
Now, Your function module is (also) available with the new API name.
old
http://my.server.com:80/epo1soa/jsonhandler/EPOFMJSON_STRING_REVERSE/Z_EPO_JSON_STRING
new
http://my.server.com:80/epo1soa/jsonhandler/stringReverse/reverse
Recommendation: keep only one service/operation, delete the unused operation and then the unused service (then, the 'old' path will not work anymore).
E.g. to read the request - http-headers, analyze the call path, etc., or to control the response. It is possible to specify HTTP headers and cookies.
Example:
...
*" REFERENCE(IT_EPO1_REQUEST_HEADERS) TYPE TIHTTPNVP
...
DATA:
ls_http_header LIKE LINE OF it_epo1_request_headers,
lt_path_elements TYPE TABLE OF string,
lv_path_element LIKE LINE OF lt_path_elements.
" get the absolute path
" use '~path_info_expanded' in order to get the relative path (starting after the SICF node name)
READ TABLE it_epo1_request_headers INTO ls_http_header WITH TABLE KEY name = '~path'.
SHIFT ls_http_header-value LEFT DELETING LEADING '/'.
SPLIT ls_http_header-value AT '/' INTO TABLE lt_path_elements.
Especially, when there is a lot of log entries, the usage of the foreign keys may help to filter for / find the right line of interest.
When calling the webservice, simply supply some key fields of interest. Preferrably - use the same FKEY field for the same kind of value, if there are multiple webservices used for similar data (like order creation, order change, order read ..) they should put the order number each into the same FKEY field.
Example:
...
lo_json->call_service(
EXPORTING
is_request_data = ls_request
iv_fkey1 = CONV #( iv_order_number )
iv_fkey2 = CONV #( iv_position_number )
IMPORTING
es_response_data = ls_response
et_response_headers = lt_response_headers
es_callstatus = ls_callstatus ).
...