EPO Consulting Wiki - EPO Connector - how to consume a simple JSON webservice


This documentation shows an easy example how to consume a JSON webservice. There is also an example how to provide a JSON webservice.

The main class used it /EPO1/CL_JSON_BASE_OUT.

The SAP Outbound call is done with function module /EPO1/EPOCLIENT.

Please note, that this webservice can be called without authentication.

Example using a freely available web service

The following example takes some parameters encoded as the URL parameters and returns a response in JSON format:
http://api.geonames.org/citiesJSON?north=44.1&south=-9.9&east=-22.4&west=55.2&lang=de&username=demo

The response looks like this

ClipCapIt-200319-180859.PNG

which can be formatted to better readability:

ClipCapIt-200319-180945.PNG

Which reads as:

  • the response contains one object of the name geonames
  • which is a table
  • which contains a structure
  • whose elements are lng, geonameId, countrycode, etc.

Error messages are formatted like this

{
    "status": {
        "message": "the daily limit of 20000 credits for demo has been exceeded....",
        "value": 18
    }
}

Which reads as:

  • the response contains one object of the name status
  • which is a structure
  • whose elements are message (a string) and value (an integer)


Now, let us try to call this service from SAP - using following steps:

  • verify, that the server is reachable by your SAP system
  • customize EPO service / operation
  • define the field mapping (JSON -> ABAP)
  • create a report to call the service


Structure of the EPO customizing for an outbound service:

  • an operation designates a special connection (maps to an URL / API path of an external server)
  • a operation is assigned to a service name; usually, such a service name (for outgoing services) has to be configured for a test- und a productive system, so we would need two service names
  • a service ID (=the same ID is used for all SAP instances) collects all service names, the mapping is done by means of the SAP system ID (sy-sysid); a combination of service name/operation will be used within a report/function module/class in order to access an outgoing service

 

Verify the network connection

Transaction SM59, create a new (test-)connection, which is only required to prove, that the connection is possible (maybe, You will need to configure a firewall, a proxy server etc.).

Create a HTTP connection to an external server:

ClipCapIt-200319-184240.PNG
  • name: specify a name, which describes the connection
  • connection type: 'G'
  • host: the server name
  • Service no: the port (in our case: 80)
  • path prefix: supply the path, if the path is constant; in our example, the path contains variables and will be supplied by the report
  • proxy host, proxy port: supply the data, if You have to use a proxy server in order to connect to the internet
  • in case of a secured connection (https), You have to specify the certificate container, import the server certificate into STRUST and restart the ICM service

Finally: save and press the 'connection' button

ClipCapIt-200320-113820.PNG
ClipCapIt-200319-184848.PNG

Every response from the remote server is a 'success', even if it is an error message (from that server). The popup for the geolocation - server means, that the server has been reached and asks for authentication. This is a sufficiently good result for the connection test. It is not necessary to enter any data, just close the dialog.

If You get a connection error (server not found, not allowed, certificate untrusted..), consult Your network team for help.

 

Customizing EPO connector

Creation of number ranges for EPO logging

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

  • EPO Connector Configuration
  • Maintain default number range /EPO1/NOR


Create the intervals '00' and '01'':

  • 00: 0000100000000000 0000199999999999
  • 01: 0000200000000000 0000299999999999

 

Creation of an EPO service name (depending on the SY-SYSID)

Usually, a SAP topology consists at least of an development/testsystem an a productive system - and the webservice might also expose a test- and a productive system. In order to distinguish the target URL (servername) for different environments, we need to prepare different service names:

  • suffix '_test' for test systems
  • suffix '_prod' for productive systems

In this simple case, we only have one service name (because the target server provides only one URL) and are using the suffix '_test'.


Transaction /EPO1/EXC

  • EPO Connector Area Menu
  • EPO Connector Configuration
  • Maintain EPO Connector services

We choose the service name (case sensitive!) ZJSON_GEO_WEBSERVICE_test:

ClipCapIt-200320-110202.PNG


Important fields:

  • Service name: the name of the service, in our case ZJSON_GEO_WEBSERVICE_test
if the geo location would expose also an productive server, You would name the service name ZJSON_GEO_WEBSERVICE_prod for that server
  • Direction: O - an outbound service, SAP will call a service
  • Operation mandatory: the server name is linked to an operation (therfore required to specify an operation)
  • the remaining fields are only for documentation

 

Creation of an EPO service ID (independent of the SY-SYSID)

The service ID will be mapped to a service name, using the current SAP system ID (SY-SYSI).

In our small case, only one system will be used. We are mapping the 'test/prod' - name to a service ID, which can be used consistent on all SAP systems in Your topology. The service ID should be the same as the service name, but without the suffix '_test' or '_prod'.

Transaction /EPO1/EXC

  • EPO Connector Area Menu
  • EPO Connector Configuration
  • Outbound Service Configuration
  • Maintain service assignment by SY-SYSID
ClipCapIt-200320-110834.PNG

Important fields:

  • SAP system id: the system ID of the used SAP system
  • Service name id: the service ID (without the suffix)
  • Service name: the service name (with suffix)

 

Creation of an EPO (outbound) operation

Create such an outbound operation for each service name (in our case, only for the 'test' name).

Transaction /EPO1/EXC,

  • EPO Connector Area Menu
  • EPO Connector Configuration
  • Outbound Service Configuration
  • EPO Client
  • Out: Maintain EPO Client service configuration
ClipCapIt-200320-111423.PNG


Important fields:

  • Service name: use the name from the service configuration (with the suffix, case sensitive!)
  • Operation: supply a nice name, in our case: cities
  • Number range for the logging
  • Processing type: synchronous (immediate processing)
  • Logging: Message for inbound (because GET does not send any daty), meta data for outbound
  • HTTP host, HTTP port: servername and port (in our case: port 80, an unsecured connection)
  • HTTP proxy settings: use the same settings as in the SM59 test connection
  • HTTP scheme: 1 HTTP (unsecured)

 

Field name mapping - /EPO1/FIELD_MAP

All the names of the JSON 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. Usually, the field mapping is required, when You POST some data to a webservice, which requires lower case fieldnames; in our case, we only GET data, which does not really require an explicit mapping (lower case letters would be matched to uppercase letters in ABAP fieldnames).

For this example, we will simply use the [camelCase] - mapping: every JSON-uppercase letter will be mapped to an ABAP letter, leaded by an underscore '_'. Only the inbound direction has to be maintained, because we have a GET request

ClipCapIt-200420-154053.PNG


Note: use the service _ID_ (without the suffix)

 

Static field mapping

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).

 

ABAP code to call the service

The following code can be used in a report, a function module or in a method of a class.

 

ABAP structure

The structure has to consist of a table with the fields (according to the selected field mapping - '[camelCase]') and additionally the structure for error messages:

  " definition of the request structure
  " assign field names according to the '[camelCase]' mapping
  TYPES:
    BEGIN OF lty_geoname_struc,
      lng          TYPE p,
      geoname_id   TYPE i,
      countrycode  TYPE string,
      name         TYPE string,
      fcl_name     TYPE string,
      toponym_name TYPE string,
      fcode_name   TYPE string,
      wikipedia    TYPE string,
      lat          TYPE p,
      fcl          TYPE string,
      population   TYPE i,
      fcode        TYPE string,
    END OF lty_geoname_struc,

    " definition of the table
    lty_geoname_tab TYPE STANDARD TABLE OF lty_geoname_struc,

    BEGIN OF lty_status_struc,
      message TYPE string,
      value   TYPE i,
    END OF lty_status_struc.


  DATA:
    " definition of the response structure
    BEGIN OF ls_response,
      geoname TYPE lty_geoname_tab,  " usable data
      status  TYPE lty_status_struc, " error info
    END OF ls_response,

    " definition of other data
    lo_json       TYPE REF TO /epo1/cl_json_base_out,
    ls_callstatus TYPE /epo1/callstatus,
    ls_geoname    TYPE lty_geoname_struc.

 

call the service

In this example, the API path is hard coded. Imagine Your own logic how to create another path.

  " init
  CREATE OBJECT lo_json
    EXPORTING
      iv_service_id_outbound = 'ZJSON_GEO_WEBSERVICE'
      iv_service_operation   = 'cities'.

  " call the webservice
  lo_json->call_service(
    EXPORTING
      iv_api_path    = '/citiesJSON?north=44.1&south=-9.9&east=-22.4&west=55.2&lang=de&username=demo'
      iv_http_method = 'GET'
    IMPORTING
      es_response_data = ls_response
      es_callstatus    = ls_callstatus ).

 

check for connection errors

  IF ( ls_callstatus-type = 'W' ) OR
     ( ls_callstatus-type = 'E' ).
    " error handling, webservice not reached
    WRITE: ls_callstatus-type,
           ls_callstatus-subject,
           ls_callstatus-description.
    RETURN.
  ENDIF.

 

check for errors in the response data

  " check for status message
  IF ls_response-status-message IS NOT INITIAL.
    " error handling, webservice returned errors
    WRITE: / ls_response-status-message,
           / ls_response-status-value.
    RETURN.
  ENDIF.

It might be possible to look into the HTTP response headers, retrieve the HTTP code and HTTP reason and other information, but we skip this data processing in this simple showcase.

 

success - use the response data

  " success: use the LS_RESPONSE - structure
  LOOP AT ls_response-geoname INTO ls_geoname.
    " so something with the data..
    WRITE: / ls_geoname-lng,
           / ls_geoname-geoname_id,
           / ls_geoname-countrycode,
           / ls_geoname-name,
           /.
  ENDLOOP.

 

further steps..

Booleans

In JSON, a boolean will be represented as 'true' or 'false', while ABAP uses the values 'X' or ''.

Use the data type XSDBOOLEAN or a data type with the domain XSDBOOLEAN for such fields.

 

Authentication

Already prepared is the basic authentication (see the instance methods 'ADD_HTTPHEADER_AUTH_BASIC( )' and 'GET_SERVICE_PARAMETER( )'). Redefine those methods, it the supplied coding does not match Your needs.

For the default coding: maintain the username and password with transaction /EPO1/EC_EB_WSSP12 for each SAP system and EPO service. Both values are stored in encoded form on the database.

Field Description
SAP System the SAP system ID
Service the EPO service ID (without suffix)
Name Parameter name ('UserName' for user name, 'UserPw' for the password)
Value the value for the parameter (enter the right user name and the password)

If there are other authorization methods required, redefine the class and add your own method which supplies the required authorization data.

For the authentication with OAuth 2.0, see OAuth authentication.

 

HTTPS, server certificate

During the handshake when starting a secured connection ('https'), the remote server will present its own public server certificate. By default, SAP will not trust any certificate and disrupts the connection process.

A connection test (SM59) will fail with the message, that SAP does not trust the certificate.

It is necessary to store certificates of trusted servers into a PSA (a container for certificates).


Transaction STRUST, change to edit mode;

  • double click on a client certificate container (SAP is on the client side of the connection)
ClipCapIt-200320-174116.PNG
in the RFC configuration (SM59) and in the EPO configuration (configout), the anonymous container will be designated by the name 'ANONYM', where the standard container is assigned to 'DFAULT' (this is our standard container for server certificates)
  • import a server certificate
  • add the certificate into the container
  • save
  • to be sure: restart STRUST and check, that the certificate is really stored in the right container
ClipCapIt-200320-174611.PNG

 

restart the ICF service

WARNING: on a productive system, do a restart outside Your main business hours, better in an anounced maintenance window (surely, You won't interrupt Your own business!)

The restart of the ICM service will refresh the list of trusted certificates according to STRUST.

Transaction SMICM

  • Administration
  • ICM
  • Restart
  • Yes (prepare a restart)

and then

  • Administration
  • ICM
  • Exit Soft
  • Global (restart the ICM on all SAP instances)


Note: 'Exit Soft' will finish currently running processes (this may take some time); 'Hard Shut Down' will immediately stop running processes, with the risk of losing (important?) data.

After the restart, return to SM59 and retry the secured connection.

 

POST data

Specify a field mapping for the outbound direction in table /EPO1/FIELD_MAP.

Similar to the response structure, define a structure which maps to the request structure of the webservice. Fill this structure and pass it to the method call:

  " have a structure LS_REQUEST, filled with request data
  " call the webservice
  lo_json->call_service(
    EXPORTING
      is_request_data = ls_request
      iv_api_path     = '/citiesJSON?north=44.1&south=-9.9&east=-22.4&west=55.2&lang=de&username=demo'
      iv_http_method  = 'POST'  " use POST instead of GET
    IMPORTING
      es_response_data = ls_response
      es_callstatus    = ls_callstatus ).

 

add HTTP request headers

In rare cases, where the customizing of static defined HTTP headers and the dynamic creation of HTTP headers (in redefined method ADD_HTTPHEADER) is not sufficient, HTTP headers for the request can be passed to the methods CALL_SERVICE and CALL_SERVICE_DOWNLOAD.

This may be useful for requests, where an authorization - token has to be passed as HTTP header.

EPO - Log

Transaction /EPO1/EXC

  • EPO Connector Area Menu
  • EPO Connector Data
  • List and view stored messages

Select the data of Your choice:

  • service name: use the service name ZJSON_GEO_WEBSERVICE_test (with suffix, case sensitive)
  • creation date (use the current day)


A click on a 'META' hotspot will show a popup with the metadata (URL, HTTP headers..):

ClipCapIt-200320-173306.PNG


A double click on a line with message data ('DataLength' greater than 0) will show a popup with the JSON data; the button 'Pretty print' will try to format a JSON message for better readability

ClipCapIt-200320-173336.PNG


Please note, that METADATA for input messages could also help to solve issues, as they contain sometimes additional information. Change the log settings for meta data to 'X' in order to log for both directions (see screenshot).

 

complete report sourcecode

Just to have a really complete coding (for simple copy/paste) of the report 'ZJSON_GEO_WEBSERVICE':

*&---------------------------------------------------------------------*
*& Report ZJSON_GEO_WEBSERVICE
*&---------------------------------------------------------------------*
*& example, how to call a JSON webservice with the EPO connector
*&---------------------------------------------------------------------*
REPORT ZJSON_GEO_WEBSERVICE.

  " definition of the request structure
  " assign field names according to the 'camelCase' mapping
  TYPES:
    BEGIN OF lty_geoname_struc,
      lng          TYPE p,
      geoname_id   TYPE i,
      countrycode  TYPE string,
      name         TYPE string,
      fcl_name     TYPE string,
      toponym_name TYPE string,
      fcode_name   TYPE string,
      wikipedia    TYPE string,
      lat          TYPE p,
      fcl          TYPE string,
      population   TYPE i,
      fcode        TYPE string,
    END OF lty_geoname_struc,

    " definition of the table
    lty_geoname_tab TYPE STANDARD TABLE OF lty_geoname_struc,

    BEGIN OF lty_status_struc,
      message TYPE string,
      value   TYPE i,
    END OF lty_status_struc.


  DATA:
    " definition of the response structure
    BEGIN OF ls_response,
      geoname TYPE lty_geoname_tab,  " usable data
      status  TYPE lty_status_struc, " error info
    END OF ls_response,

    " definition of other data
    lo_json       TYPE REF TO /epo1/cl_json_base_out,
    ls_callstatus TYPE /epo1/callstatus,
    ls_geoname    TYPE lty_geoname_struc.


  " init
  CREATE OBJECT lo_json
    EXPORTING
      iv_service_id_outbound = 'ZJSON_GEO_WEBSERVICE'
      iv_service_operation   = 'cities'.

  " call the webservice
  lo_json->call_service(
    EXPORTING
      iv_api_path    = '/citiesJSON?north=44.1&south=-9.9&east=-22.4&west=55.2&lang=de&username=demo'
      iv_http_method = 'GET'
    IMPORTING
      es_response_data = ls_response
      es_callstatus    = ls_callstatus ).

  IF ( ls_callstatus-type = 'W' ) OR
     ( ls_callstatus-type = 'E' ).
    " error handling, webservice not reached
    WRITE: ls_callstatus-type,
           ls_callstatus-subject,
           ls_callstatus-description.
    RETURN.
  ENDIF.

  " check for status message
  IF ls_response-status-message IS NOT INITIAL.
    " error handling, webservice returned errors
    WRITE: / ls_response-status-message,
           / ls_response-status-value.
    RETURN.
  ENDIF.

  " success: use the LS_RESPONSE - structre
  LOOP AT ls_response-geoname INTO ls_geoname.
    " so something with the data..
    WRITE: / ls_geoname-lng,
           / ls_geoname-geoname_id,
           / ls_geoname-countrycode,
           / ls_geoname-name,
           /.
  ENDLOOP.