You are on page 1of 68

"SAP ABAP on HANA"

***************************************************
SAP ABAP on HANA
Getting Started with the ABAP RESTful Application
Programming
Getting Started with the Model (RAP) Application
ABAP RESTful
Getting Started with the ABAP RESTful Application

Programming Model (RAP)

The ABAP RESTful Applica2on Programming Model (short: RAP) offers developers an efficient way to build
enterprise ready, SAP HANA-op2mized, OData-based Fiori UI services and Web APIs in the cloud as well as
on-premise. It is the evolu2onary successor to the ABAP Programming Model for SAP Fiori.

The ABAP RESTful Programming Model is generally available in the SAP BTP ABAP Environment, and now
also in SAP S/4HANA star2ng with the edi2on 1909. The feature scope in SAP Business Technology
PlaVorm (BTP) ABAP Environment – formerly known as SAP Cloud PlaVorm ABAP environment – is
enhanced on a quarterly basis on defined dates, while new capabili2es are delivered in SAP S/4HANA on a
yearly basis with new on-premise edi2ons.
The greenfield development of OData-based services – i.e., star2ng from scratch – is supported with the
socalled managed implementa2on type, whereas the brownfield development – i.e., based on exis2ng
code – is supported with the so-called unmanaged implementa2on type.

The key players in the ABAP RESTful Applica2on Programming Model are:

ABAP Developments Tools in Languages: ABAP Powerful


Eclipse and CDS frameworks
The ABAP RESTful Applica2on Programming Model comes with a set of development guides and best
prac2ces to facilitate the developer onboarding. The best prac2ces are provided in the form of ready-to-
use examples delivered with the ABAP Flight Reference Scenario (refer to the appropriate sec2on below).
Availability:

• SAP BTP ABAP Environment


• SAP S/4HANA 1909 and higher

Roadmap:

Check the ABAP PlaVorm Roadmap

Developing a REST API in ABAP


The ICF Tree
First Strategy: HTTP Request Method
Second Strategy: Data Transfer Format
The Common Plot for All Requests
A Specific Task – the PUT Request
Session, Iden2ty and Locking
Using ABAP’s Built-In JSON Converter Summary

In two recent blogs, I demonstrated how to write web clients of REST APIs – with XML (demo applica2on
here) or JSON (demo applica2on here) as data transfer format. In this blog, I will focus on the server
side: How to implement a REST API as ABAP request handler. You can inspect all the code I am discussing
here on the MIGROS BSP website: It’s all in Class ZCL_JOB_DATA .

The ICF Tree


Request handlers are classes implemen2ng the interface IF_HTTP_EXTENSION, which consists of one
single method HANDLE_REQUEST. A request handler class can be afached to a path in transac2on SICF.
An incoming HTTP request will be analyzed by the Internet Communica2on Framework, trying to match
the request path against the SICF path. The match process is stopped as soon as a node with an afached
request handler is found. If this is the case, an instance of the handler class will be created, and the
method HANDLE_REQUEST will be called.

Our example service is afached to the path /job/afributes. The class ZCL_JOB_DATA is declared to be
responsible for all incoming requests where the request path starts with /job/afributes :
First Strategy: HTTP Request Method
The implementa2on of the interface method if_hfp_extension~handle_request() forms the uppermost
level of processing. Therefore, the implementa2on only gives the rough processing skeleton: An instance
for database opera2ons, as well as an instance for the processing of the REST opera2on are created, the
request handling is delegated to that instance, and there is a catch block for error processing in case that
no instance could be determined for processing the request. Such a situa2on should result in an HTTP
response with status code ‘400 – Bad Request’.

At this place, we are using the Strategy design pafern: Depending on the HTTP method (GET, PUT, POST,
DELETE, OPTIONS), a specific instance is created. Each possible instance corresponds to a specific strategy.
method if_hfp_extension~handle_request .

data: lo_db type ref to lif_db, lo_rest type


ref to lif_rest, lo_invalid_method type ref to zcx_error,
lv_reason type string. try.

* Object for database opera2ons lo_db ?= get_db(


io_server = server ).

• Get the correct rest handler instance, depending on the verb (GET, PUT, POST, O lo_rest ?=
get_rest( io_server = server io_db = lo_db ).
• Do the opera2on lo_rest->handle_request( ). catch zcx_not_found into

lo_invalid_method.lv_reason = lo_invalid_method->get_text( ). server->response-

>set_status( code = 400 " Bad Request reason = lv_reason ).

• endtry. endmethod.

We are using a naming conven2on for the instance determina2on: The class LCL_REST_GET will be
associated with HTTP verb GET, LCL_REST_PUT with PUT, and so on. All these classes implement the
interface LIF_REST. This way, we can use dynamic instance crea2on. Alterna2vely, we could have wrifen a
large CASE … statement with many WHEN’s. The advantage of the CASE would be that the create object
statement could be sta2cally checked for syntac2cal correctness. I have chosen the dynamical variant
since I find it clearer and more readable than a bunch of WHEN branches.
Observe that the HTTP request method (GET, PUT, POST, …) is available as pseudo header field with the
name ‘~request_method’:

method get_rest. data: lv_classname type seoclsname, lv_method type string,


lv_message type text255. lv_method = io_server->request->get_header_field(
'~request_method' ). concatenate 'LCL_REST_' lv_method into lv_classname. try.
create object eo_rest type (lv_classname)
expor2ng
io_request = io_server->request io_response =
io_server->response io_db = io_db. catch
cx_sy_create_object_error. lv_message = 'Method ''&'' not
supported'(001). replace '&' in lv_message with lv_method.
_raise_with_text zcx_not_found lv_message.
endtry.
endmethod.

Second Strategy: Data Transfer Format


Now we have different handler classes for the different HTTP request methods. But for all these handlers,
there are some common tasks. One of these common tasks is: to determine the current data transfer
format, and to convert the input – if available – into ABAP data, and vice versa: to convert the ABAP result
data into the output with the desired data transfer format (XML or JSON).

Now, some request methods like GET do not require any request content. So the conversion of incoming
data is performed by those method handlers that know they require content data. On the other hand,
there will always be a result of the following data type:

types: begin of ty_result,


msgtype type symsgty,
message type c length 255,
jobs type zjobs_tab, end
of ty_result.

There may not always be entries in the job table. But not every component of this structure will be ini2al.
If there is no job table, then usually there will be a message. So the conversion of the result can always be
performed.
It makes sense to work with an abstract converter class, the specific subclasses containing the conversion
algorithms per content-type. This is the second applica2on of the Strategy pafern.
class lcl_converter defini2on abstract.
public sec2on. class-methods
get_instance
impor2ng
iv_accept type string returning value(eo_instance) type ref to
lcl_converter. methods content_type abstract returning
value(ev_content_type) type string. methods get_entered_data abstract
impor2ng
iv_cdata type string
expor2ng es_job type zjobs
raising zcx_parse_error. methods
result_to_cdata abstract
impor2ng
is_result type ty_result

expor2ng ev_cdata type string. endclass.


"lcl_converter DEFINITION

The sta2c method LCL_CONVERTER=>GET_INSTANCE( ) makes the dis2nc2on, depending on the Accept
header field of the HTTP request:

class lcl_converter implementa2on. method get_instance. if


iv_accept cs 'applica2on/json'. create object eo_instance type
lcl_json_converter.
else.
create object eo_instance type lcl_xml_converter. endif.
endmethod. "get_instance
endclass. "lcl_converter IMPLEMENTATION

The Common Plot for All Requests


We can extract common tasks into a superclass lcl_rest of all specific method handlers,
implemen2ng the interface lif_rest~handle_request( ) once for all subclasses.
The common code in the superclasse needs to be mixed with specific code, implemented in the subclass
and defining the specific behaviour of that subclass. To achieve this, we call at the desired point of 2me in
lif_rest~handle_request(), an abstract method do( ), which has to be redefined in the subclasses. This do()
method will contain the specific ac2on.

Now, the common implementa2on lif_rest~handle( ) in the superclass only defines the flow of the
processing, leaving the concrete ac2ons to the subclasses or to delegates like go_converter:

1. Execute the specific ac2on by calling do(),


2. Error handling, with HTTP error code 400 “Bad Request” in case of conversion error (wrong
incoming data), or sexng response data for an error message in case of an applica2on error,
3. The result structure is mapped to the response data structure (XML or JSON), using the
corresponding converter instance,
4. Finally, the response data is placed into the body of the HTTP response, and also the appropriate
response type is set: applica2on/json, or text/xml.

This is the general sketch – the response processing that is valid for all HTTP request methods and for all
content types (XML as well as JSON). The details are contained in the called methods.

method lif_rest~handle_request.

data: lo_ex type ref to cx_root, lv_cdata type string,


ls_result type ty_result.

try.

* Execute the specific opera2on do( impor2ng es_result = ls_result ).

catch zcx_parse_error into lo_ex. go_response->set_status( code = 400 "


Bad request reason = lo_ex->get_text( ) ).
set_response_parameters( ).
return. catch zcx_error into
lo_ex.
ls_result-message = lo_ex->get_text( ). ls_result-

msgtype = 'E'. endtry.

* Convert result structure into JSON or XML, respec2vely


call method go_converter->result_to_cdata
expor2ng
is_result = ls_result
impor2ng ev_cdata =
lv_cdata.

* Place the result in the response body


call method set_response
expor2ng
iv_content_type = go_converter->content_type( ) iv_cdata = lv_cdata.

endmethod. "handle_request

A Specific Task – the PUT Request


Let’s look at a specific task for illustra2on: The PUT request – which always is a task to update or insert job
afributes for a given ID on the database. As follows from the design, there is an own local class
LCL_REST_PUT handling PUT requests. Actually, for this request handler, there was only the do method
itself to implement (which is the absolute minimum for a specific task class to implement: do() is abstract
in the parent class. Without an implementa2on, no instances could be built.):

class lcl_rest_put defini2on inheri2ng from lcl_rest. protected


sec2on. methods do redefini2on. endclass.
"lcl_rest_put DEFINITION

The implementa2on goes as follows:

The job with the specified ID is read from the database (if an ID was specified – for new jobs, this is
not the case),
The entered data will be parsed into an ls_job structure, using the appropriate go_converter instance,
And finally, the save() method is called. It is implemented in the superclass, since other request
methods use it, too.
class lcl_rest_put implementa2on. method do. data: ls_job type
zjobs, lv_id type zjobs-id. try. get_job_by_id( impor2ng
es_job = ls_job ). lv_id = ls_job-id. catch zcx_not_found.
endtry.

clear ls_job. call method go_converter-


>get_entered_data
expor2ng
iv_cdata = go_request->get_cdata( )
impor2ng
es_job = ls_job.

if ls_job is not ini2al. if lv_id is not ini2al. ls_job-id =


lv_id. endif. save( changing cs_job = ls_job ). es_result-
message = 'Job & has been saved'(002). replace '&' in
es_result-message with ls_job-id. es_result-msgtype = 'S'. "
success message insert ls_job into table es_result-jobs.
endif.

endmethod. "do
endclass. "lcl_rest_put IMPLEMENTATION

Note that the implementa2on of this task doesn’t care about the HTTP data structure, the format actually
in use, nor about the details of the transfer data format. It simply works with ABAP data structures ls_job
for the input and es_result for the output.

Session, Iden:ty and Locking


In the test applica2ons (neither in the JSON app nor in the XML app), there is neither login nor enqueue
of the data. Since the applica2ons are open for everybody, this works only since I don’t really operate on a
database table ZJOBS. Actually, each client who calls the applica2on is working with his own session data,
so he doesn’t conflict with other users’ opera2ons, and is himself not disturbed by other users. The
session data are preserved for him as server-side cookies, surviving the single dialogue step (for example
reloading the page would reproduce the current state of the data).
When a web-client is wrifen as BSP, there is a session-ID available in the afribute run2me->server_id.
This session ID iden2fies the par2cular browser instance that made the request. On the client-side, this
session-ID is always contained in a cookie called sap-appcontext. If an applica2on has state which has to
be preserved with a session ID, the ID has to be extracted from the sap-appcontext cookie and has to be
passed as a query parameter with all the Ajax requests. Here is the func2on which extracts the sap-
appcontext from the cookie:
func2on get_appcontext() {
var lAppcontextCookie = document.cookie.match(/sap-appcontext=(.*?)(?:;|$)/);
return lAppcontextCookie &&
( lAppcontextCookie.length >= 2) &&
unescape( lAppcontextCookie[1] ) || ""; }

The appcontext returned from this func2on, can be passed as query parameter with every Ajax request.
On the server side, the session ID can be extracted from that parameter:

method get_session_id.

data: lv_app_context type string, lv_app_context64


type string.

* Read the form field, provided by the Ajax request lv_app_context64 = io_server-

>request->get_form_field( 'sap_appcontext' ). if lv_app_context64 is not ini2al.

* Base64 decode lv_app_context = cl_hfp_u2lity=>decode_base64( lv_app_context64


).

* Extract the Session-ID


find regex 'sap-sessionid=([^;]+)(?:;|$)'
in lv_app_context

submatches ev_session_id. endif.

if ev_session_id is ini2al. ev_session_id =


io_server->session_id. endif. endmethod.

As a fallback, in line 22, the server->session_id is used. However, there will be a new server->session_id
for each request, which results in fresh session data with each dialogue step. If you really need session
management, it is essen2al that the session id is passed to the server.
It is a good idea to combine the session id with the login procedure: If the user authen2cates, his browser
receives a sessionid with a limited validity. That session-ID has to be passed with each successive REST
opera2on. In ABAP, it can be used to
store and retrieve session-specific data in the database table SSCOOKIE, via its database access class
CL_BSP_SERVER_SIDE_COOKIE.

This coupling of a session id with login is – roughly – the way how the REST API for the HP Quality Center
works.

Using ABAP’s Built-In JSON Converter


While the XML converter instance is prefy straighVorward to implement – calling an XSLT transforma2on
for XML -> ABAP, and another one for the way back – it might come as a surprise that the JSON conversion
can be handled exactly the same way: with transforma2ons. This is possible since the call transforma2on
statement supports the JSON format (at least as per SAP_BASIS 702). JSON is auto-detected and parsed
into an intermediate JSON-XML format. This can be processed with an arbitrary XSLT transforma2on, and
converted into other XML documents or to ABAP data.

For example, a PUT request from our test applica2on may send the following JSON data to the server:

{
"ID": "0001",
"REPID":"RSNAST00",
"VARID": "UXPD_KUBE_KV",
"PRIO": "2",
"RESTART": "X",
"DESCR": "Output all sales order confirma2ons",
"CONTACT": "Rainer
Zufall" }

If a string with this content is passed as “SOURCE XML” to ABAP’s CALL TRANSFORMATION statement, the
JSON will be parsed into an XML representa2on like this one (the format is easy to understand – I think a
detailled explana2on is not necessary here):

<?xml version="1.0" encoding="uV-8"?>


<object>
<str name="ID">0001</str>
<str name="REPID">RSNAST00</str>
<str name="VARID">UXPD_KUBE_KV</str>
<str name="PRIO">2</str>
<str name="RESTART">X</str>
<str name="DESCR">Output all sales order confirma2ons</str>
<str name="CONTACT">Rainer Zufall</str>
</object>

When processing an arbitrary XSLT transforma2on, with the CALL TRANSFORMATION statement, and
passing a JSON string as source, the XSLT will operate on this internal JSON-XML representa2on. It is easy
to transform such a JSON-XML document into ABAP data – to be more precise: to transform it into an
asXML representa2on of ABAP data. For example, consider the following XSLT transforma2on:

<xsl:transform version="1.0" xmlns:xsl="hfp://www.w3.org/1999/XSL/Transform">


<xsl:template match="/">
<asx:abap xmlns:asx="hfp://www.sap.com/abapxml" version="1.0">
<asx:values>
<JOB>
<xsl:apply-templates/>
</JOB>
</asx:values>
</asx:abap>
</xsl:template>
<xsl:template match="str">
<xsl:element name="{@name}"> <xsl:value-
of select="."/>
</xsl:element>
</xsl:template>
</xsl:transform>

When applied to the JSON string, it will produce the following result:

<?xml version="1.0" encoding="UTF-8"?>


<asx:abap xmlns:asx="hfp://www.sap.com/abapxml" version="1.0">
<asx:values>
<JOB>
<ID>0001</ID>
<REPID>RSNAST00</REPID>
<VARID>UXPD_KUBE_KV</VARID>
<PRIO>2</PRIO>
<RESTART>X</RESTART>
<CONTACT>Rainer Zufall</CONTACT>
<DESCR>Output all sales order confirma2ons</DESCR>
</JOB>
</asx:values>
</asx:abap>
This is a valid ABAP data descrip2on. If the transforma2on is named ZJSON2JOB, the data can simply be
imported into an ABAP data structure with the components ID, REPID, and so on – as is the structure
es_job in the following implementa2on of the JSON converter.

class lcl_json_converter implementa2on. method

get_entered_data. data: lo_ex type ref to

cx_transforma2on_error.

clear es_job. check

iv_cdata cn space. try.

call transforma2on zjson2job


source xml iv_cdata result
job = es_job.

catch cx_transforma2on_error into lo_ex.

raise_parse_error( lo_ex ). endtry.

endmethod. "get_entered_data
Many things can be done with the iden2ty transforma2on ID, with no need to define an own XSLT
transforma2on at all. If you can impose the JSON data structure to be used in the web applica2on, it is of
advantage to use such a “canonical” structure. For example, consider wrapping the JSON hash with the
job afributes into another hash, making it the value for some symbolic key name like “JOB”:

{
"JOB": {
"REPID": "RSNAST00",
"VARID": "UXPD_KUBE_KV",
"PRIO": "2",
"RESTART": "X",
"DESCR": "Output all sales order confirma2ons",
"CONTACT": "Rainer Zufall",
"ID": "0001"
}
}

Then the data could be parsed into a structure without the need of developing a custom XSLT
transforma2on, simple using the iden2ty:

call transforma2on id
source xml iv_cdata
result job = es_job.

In this example, since I have wrifen the web-client and the server-side processing, I could have chosen
this more “canonical” format. But by not chosing it, I learned how to work with more flexible JSON data
formats.

There are several reasons for working with “non-canonical” JSON representa2ons of ABAP data:

A JSON format may be designed in favour of the web applica2on – to op2mize the readability of the
client JavaScript code working on the data.
There may be client components requiring a par2cular JSON formats. For example, the jQuery
datatable requires the table data to be passed as an array of arrays:
hfp://www.datatables.net/releasedatatables/examples/data_sources/ajax.html
JSON-based third party services may be called from the ABAP side (with a HTTP client object)
ABAP data may be projected to the essen2al data, reducing the message size to the data which are
really needed.

Just to illustrate, let’s have a look at the other conversion – the way out from the server to the client.
Again, the format differs slightly from the “canonical” JSON format, which would simplify the ABAP-side
handling considerably. As men2oned, the result data structure contains

a
message,
a
message
type, and
a table of
job
afributes
:

types: begin of ty_result,


msgtype type symsgty,
message type c length 255,
jobs type zjobs_tab, end
of ty_result.

The following format would be a perfect JSON pendant for this structure. It could be simply produced
with the iden2ty transforma2on, passing as “source result = ls_result” (where ls_result is a structure of
type ty_result):

All the component names match perfectly with the JSON hash key names,
An internal table is mapped as a JSON array of hashs, each hash represen2ng one
entry of the table, And there is a top level hash with a symbolic name “RESULT” for
the complete thing:

{
"RESULT": {
"MSGTYPE": "I",
"MESSAGE": "Test",
"JOBS": [
{
"ID": "0001",
"REPID": "ZZTEST",
"VARID": "VARI1",
"PRIO": "1",
"RESTART": "X",
"CONTACT": "Harry Haller",
"DESCR": "A hopeless job"
},
{
"ID": "0002",
"REPID": "ZZTEST2",
"VARID": "VARI2",
"PRIO": "3",
"RESTART": "",
"CONTACT": "Peter Pan",
"DESCR": "A juvenile job"
}
]
}
}

But the JSON format that the REST API supports, actually differs in some details:

The jobs are designed not as an array, but as a hash, with


the ID as hash key.
There is no redundant hash, wrapping the whole thing as the
value for some key.
The component for MSGTYPE is different. It is simply called TYPE.

Here is an example instance:

{
"JOBS": {
"0001": {
"REPID": "RSNAST00",
"VARID": "UXPD_KUBE_KV",
"PRIO": "2",
"RESTART": "X",
"CONTACT": "Rainer Zufall",
"DESCR": "Output all sales order confirma2ons"
},
"0002": {
"REPID": "RBDAPP01",
"VARID": "UXPD_EDI_GUT02",
"PRIO": "3",
"RESTART": "X",
"CONTACT": "Herbert Hur2g",
"DESCR": "Credit Memos"
}
},
"MESSAGE": "",
"TYPE": ""
}

We proceed in a similar way as above, only in the other direc2on: based on the ABAP data type ty_result,
we write an XSLT transforma2on to obtain the internal JSON-XML format corresponding to this JSON data
string.

The JSON-XML data format of the desired JSON data string looks like this:

<?xml version="1.0" encoding="uV-8"?>


<object>
<object name="JOBS">
<object name="0001">
<str name="REPID">RSNAST00</str>
<str name="VARID">UXPD_KUBE_KV</str>
<str name="PRIO">2</str>
<str name="RESTART">X</str>
<str name="CONTACT">Rainer Zufall</str>
<str name="DESCR">Output all sales order confirma2ons</str>
</object>
<object name="0002">
<str name="REPID">RBDAPP01</str>
<str name="VARID">UXPD_EDI_GUT02</str>
<str name="PRIO">3</str>
<str name="RESTART">X</str>
<str name="CONTACT">Herbert Hur2g</str>
<str name="DESCR">Credit Memos</str>
</object>
</object>
<str name="MESSAGE">Test</str>
<str name="TYPE">I</str>
</object>

So this is the target that has to be obtained as result of the transforma2on. On the other hand, the asXML
format of the structure ty_result looks like this:

<?xml version="1.0" encoding="uV-8"?>


<asx:abap xmlns:asx="hfp://www.sap.com/abapxml" version="1.0">
<asx:values>
<DATA>
<JOBS>
<ZJOBS>
<ID>0001</ID>
<REPID>RSNAST00</REPID>
<VARID>UXPD_KUBE_KV</VARID>
<PRIO>2</PRIO>
<RESTART>X</RESTART>
<CONTACT>Rainer Zufall</CONTACT>
<DESCR>Output all sales order confirma2ons</DESCR>
</ZJOBS>
<ZJOBS>
<ID>0002</ID>
<REPID>RBDAPP01</REPID>
<VARID>UXPD_EDI_GUT02</VARID>
<PRIO>3</PRIO>
<RESTART>X</RESTART>
<CONTACT>Herbert Hur2g</CONTACT>
<DESCR>Credit Memos</DESCR>
</ZJOBS>
</JOBS>
<MESSAGE>Test</MESSAGE>
<MSGTYPE>I</MSGTYPE>
</DATA>
</asx:values>
</asx:abap>

And this is the XSLT program that will perform the transforma2on:

<xsl:transform xmlns:xsl="hfp://www.w3.org/1999/XSL/Transform" version="1.0">

<xsl:template match="DATA">
<object>
<xsl:apply-templates/>
</object>
</xsl:template>

<xsl:template match="JOBS">
<object name="JOBS">
<xsl:apply-templates/>
</object>
</xsl:template>

<xsl:template match="ZJOBS">
<object name="{./ID}">
<xsl:apply-templates select="*[name() != 'ID']"/>
</object>
</xsl:template>
<xsl:template match="ZJOBS/* | MESSAGE">
<str name="{name()}">
<xsl:value-of select="."/>
</str>
</xsl:template>

<xsl:template match="MSGTYPE">
<str name="TYPE">
<xsl:value-of select="."/>
</str>
</xsl:template>

</xsl:transform>

We see that, basically, for each devia2on from the “canonical” JSON representa2on of ABAP data, there is
a template in the XSLT transforma2on handling this devia2on. For example, the different name TYPE
instead of MSGTYPE in the target is handled with the template

<xsl:template match="MSGTYPE">
<str name="TYPE">
<xsl:value-of select="."/>
</str>
</xsl:template>

The ID has to be rearranged: From being a simple afribute of the ZJOBS data structure, it has to be raised
one level higher to become the key of a hash. All the other afributes, except ID, are copied as string
nodes into the result. For this, these two templates are necessary:

<xsl:template match="ZJOBS">
<object name="{./ID}">
<xsl:apply-templates select="*[name() != 'ID']"/>
</object>
</xsl:template>

<xsl:template match="ZJOBS/* | MESSAGE">


<str name="{name()}">
<xsl:value-of select="."/>
</str>
</xsl:template>

Mapping the ty_result data object into a JSON string of the expected format, is now performed in ABAP
with the following code:

method result_to_cdata. data: lo_writer type ref to cl_sxml_string_writer.

lo_writer = cl_sxml_string_writer=>create( type = if_sxml=>co_xt_json ). call transforma2on zjobs2json


source data = is_result result xml lo_writer. ev_cdata = cl_abap_codepage=>convert_from( lo_writer-
>get_output( ) ).

endmethod. "result_to_cdata

That’s all: ev_cdata will then contain the JSON data string, to be placed in the HTTP response body.

Summary
I outlined some typical topics concerning the implementa2on of REST APIs in ABAP. It is possible to keep
separate concerns in separate (local or global) classes by applying paferns like strategy. This is how the
class ZCL_JOB_DATA, serving my demo REST API, is organized (the basic ideas have been discussed in this
blog):
Consume API directly into ABAP
As API is gexng famous with so many APIs available how can we consume it into ABAP directly without
any middle wares.

One of the easiest way to call an external API directly into ABAP system using API Key.

Step 1 – What do you need –

Postman – To verify if the data is coming as expected


API Key – To fetch data form source system into ABAP

SSL Cer2ficates – You can download the cer2ficate and it needs to install in SAP. In SAP, cer2ficates can be
installed using tcode Strust.

Postman – We will need the parameters to get data in ABAP

Step 2 – Cer2ficate needs to be installed in “SSL Server Standard”

Step 2 – ABAP Program


The below program has been developed on 750 but the classes exist from 730 itself and can be used
*&---------------------------------------------------------------------*
*& Report ZTEST_API
*&---------------------------------------------------------------------*
*&
*&---------------------------------------------------------------------* REPORT ztest_api.

SELECTION-SCREEN: BEGIN OF BLOCK b1 WITH FRAME TITLE TEXT-s01.


PARAMETERS: p_inv TYPE belnr_d DEFAULT '12345'. SELECTION-SCREEN:
END OF BLOCK b1.

START-OF-SELECTION.

*HTTP Client Abstrac2on


DATA lo_client TYPE REF TO if_hfp_client.

*Data variables for storing response in xstring and string


DATA : lv_xstring TYPE xstring, lv_string
TYPE string, lv_node_name TYPE string.
CLEAR : lv_xstring, lv_string, lv_node_name.
*Pass the URL to get Data lv_string =
|hfps://XXXXXX.com/api/XXXXX/{ p_inv }|.

*Crea2on of New IF_HTTP_Client Object cl_hfp_client=>create_by_url( EXPORTING


url = lv_string IMPORTING
client = lo_client
EXCEPTIONS
argument_not_found = 1
plugin_not_ac2ve = 2
internal_error = 3 ).

IF sy-subrc IS NOT
INITIAL. * Handle errors
ENDIF.

lo_client->propertytype_logon_popup = lo_client->co_disabled. lo_client->request->set_method( 'GET'


).
CALL METHOD lo_client->request->set_header_field EXPORTING name = 'XXXXXXXX' value = '
XXXXXXXX '.

CALL METHOD lo_client->request->set_header_field EXPORTING name = 'Accept' value =


'applica2on/xml'.

*Structure of HTTP Connec2on and Dispatch of Data lo_client->send( ). IF sy-subrc IS NOT INITIAL. *
Handle errors ENDIF.

*Receipt of HTTP Response lo_client->receive( ). IF sy-subrc IS NOT INITIAL. * Handle errors ENDIF.
END-OF-SELECTION.

*Return the HTTP body of this en2ty as binary data lv_xstring = lo_client->response-
>get_data( ).

**Displays XML File


CALL METHOD cl_abap_browser=>show_xml EXPORTING
xml_xstring = lv_xstring size =
cl_abap_browser=>large.

Step 4 – Output

Once the program is executed, you will be able to see the data directly in form of XML. The XML data can
also be converted into internal table and be used/displayed as per requirement
Configuring OAuth 2.0 and Crea:ng an ABAP Program That
Uses OAuth 2.0 Client API
Introduc)on:
The OAuth 2.0 server (AS ABAP) protects resources you want to use, and the OAuth 2.0 client enables you
to access services and resources that are offered by a service provider.

Authen2ca2on with OAuth 2.0 protec2on between an SAP NetWeaver Applica2on Server for ABAP and
an external service provider such as, for example, SAP HANA Cloud PlaVorm, Google Cloud PlaVorm, or
MicrosoŠ Azure, requires a dedicated OAuth 2.0 client. You can configure and register this OAuth 2.0
client in the OAuth 2.0 server (AS ABAP).

The OAuth 2.0 client enables end users to easily access a service provider with the same creden2als they
are already using in the service provider. The communica2on between OAuth 2.0 client and server is
secured by an HTTPS connec2on. The end users can then use services and resources offered by a service
provider, for example, SAP HANA Cloud PlaVorm or MicrosoŠ Azure, to edit or process their data that is
located as resources on the AS ABAP. During the authen2ca2on, the OAuth 2.0 client passes the OAuth
2.0 scopes to the service provider. The OAuth 2.0 scopes contain references to the allowed resources.

So first, lets try to understand from POSTMAN. How to call the OAuth2.0 enabled endpoint.

POSTMAN:
Use the GET call with the main API endpoint. In the authen2ca2on, select the type as ‘OAuth2.0’.

Based on the service provider, select the grant type on the right hand side. I have selected as Client
Creden2als. Provide the Access Token URL, Client ID and Client Secrete. Also provide the scope as
configured at the service provider. Select Client Authen2ca2on as ‘Send as Basic Auth header’ and click
on Get New Access Token.
Now perform the GET call and set any header parameters if required.

We get the status as 200 and response from the service provider.

Now we will call the OAuth2.0 enabled endpoint from ABAP program using OAuth2.0 configura2on.

Refer to the SAP help which has quite good amount of informa2on on the process flow and pre-

requisites. hfps://help.sap.com/viewer/3c4e8fc004cb4401a4fdd737f02ac2b9/7.5.6/en-

US/90d8fa4c8b38425aae560d1d402fe627.html

Crea-ng OAuth2.0 client profile:


1.Create OAuth2.0 client profile from SE80 as below.
1. Start the object navigator (transac2on SE80).
2. Choose Development Object in the dropdown list.
3. To create a development object in the SAP namespace, choose Create OAuth 2.0 Client Profile in
the context menu of the object name.
4. Enter the object name in the Client Profile field of the popup as ‘ZOAUTH_CLIENT_PROFILE’.
5. choose the type of service provider as ‘DEFAULT’
6. Also provide the scope as configured in the service provider configura2on and ac2vate the client
profile.
7.

Configure the OAuth2.0 Client


1. Go to transac2on OA2C_CONFIG to configure the OAuth2.0
2. Click on ‘Create’.
3. Select the OAuth2.0 Client Profile as ‘ZOAUTH_CLIENT_PROFILE’ and provide the Client ID. 4.
Maintain the Client Secrete
5. Also provide the Token Endpoint.
6. Enter the Client Authen2ca2on as ‘Basic’, Resource Access Authen2ca2on as ‘Header Field’ and
select grant type as ‘Client Creden2als’.
7. Click on save. The OAuth2.0 configura2on name is ‘ZOAUTH_CLIENT_PROFILE’
8.

Now the OAuth2.0 configura2on is completed.

Create an ABAP program that uses OAuth 2.0 Client API:


OAuth 2.0 client is used together with the HTTP/REST client in our ABAP program. It sets an OAuth 2.0
token and makes the HTTP or REST client send the token back to the program and receive it again.

The following image displays the process.


Process:

1. Create an instance of the OAuth 2.0 client type IF_OAUTH2_CLIENT.


2. Create an instance of the HTTP client type IF_HTTP_CLIENT.
Now, the OAuth 2.0 client instance is used to set the access token in the HTTP client.
3. To trigger the access token, the applica2on program calls the SET_TOKEN method in the OAuth 2.0
client instance and sends the HTTP client instance as a parameter.
4. (a and b) AŠer the access token was handed over to the HTTP client as described in step 3, use the
HTTP client to access OAuth 2.0 protected resources.

Below is the code sample:


Here populate the LV_URL with the API main endpoint. Also populate the method value as ‘GET’.

We can also create the RFC des2na2on to maintain the Main API endpoint.
Here we will use the profile name and configura2on name as ‘ZOAUTH_CLIENT_PROFILE’ to set the
OAuth2.0 token.

Data: param_kind TYPE string VALUE ‘H’.


Get the HTTP status by calling the GET_STATUS method.
Conclusion:
Using OAuth2.0 configura2on, we can call the OAuth2.0 enabled external service from ABAP program.
Call external APIs and retrieve the data in ABAP
As the ODATA protocol is gexng famous nowadays, and so many APIs avail different services/data as
ODATA service, how an ABAPer can male use of it?

Here is one of the way to call an external API and get the data into ABAP system.

Step 1. Use class CL_HTTP_CLIENT to get an object of type IF_HTTP_Client. There are different methods
for gexng this object. example: by the API URL or by HTTP Des2na2on etc. In below code snippet I have
used API URL to get the object. Once the object of type IF_HTTP_Client is received, then execute the
instance methods SEND() and RECEIVE() to establish Connec2on and Dispatch of Data and receive HTTP
Response.
REPORT zsuhas.

*HTTP Client Abstrac2on


DATA lo_client TYPE REF TO if_hfp_client.

*Data variables for storing response in xstring and string


DATA : lv_xstring TYPE xstring, lv_string TYPE
string, lv_node_name TYPE string.

CLEAR : lv_xstring, lv_string, lv_node_name.

*Put required node name into constant (case sensi2ve)


CONSTANTS : co_req_node TYPE string VALUE '<YOUR_REQUIRED_NODE_NAME>'.

*Crea2on of New IF_HTTP_Client Object


cl_hfp_client=>create_by_url( EXPORTING
url = "URL proxy_host =
"Proxy proxy_service = "Port
sap_username = "Username
sap_client = "Client
IMPORTING
client = lo_client EXCEPTIONS
argument_not_found = 1
plugin_not_ac2ve = 2
internal_error = 3 ).

IF sy-subrc IS NOT INITIAL. *


Handle errors ENDIF.

*Structure of HTTP Connec2on and Dispatch of Data lo_client->send( ).


IF sy-subrc IS NOT INITIAL. *
Handle errors ENDIF.

*Receipt of HTTP Response


lo_client->receive( ). IF sy-
subrc IS NOT INITIAL. *
Handle errors
ENDIF.

Note : If you might be asked to authen2cate while calling RECEIVE method. If so, insert the following
piece of code aŠer you get the create object of type IF_HTTP_client.

lo_client->propertytype_logon_popup = lo_client->co_disabled. data


l_username type string. data l_password type string. l_username =
MY_USERNAME. l_password = MY_PASS.

lo_client->authen2cate( expor2ng
proxy_authen2ca2on = 'X'
client = Client " R/3 System, client number from logon username = l_username "
ABAP System, User Logon Name
password = l_password " Logon ID
* language = language " SAP System, Current Language
).

Step 2 . Use get_data() method of the afribute response of object received in Step 1 to get the data in
Binary format. And store it in a variable of type xstring.

*Return the HTTP body of this en2ty as binary data lv_xstring = lo_client-
>response->get_data( ).

*Displays XML File


CALL METHOD cl_abap_browser=>show_xml
EXPORTING
xml_xstring = lv_xstring size =
cl_abap_browser=>large.
Step 3. User standard procedure for XML processing in OO ABAP. as below.

Step 4. Get the root node of your DOM structure and all its children. Use the iterator object to reach
your desired node. Once the desired node is reached, create another iterator object to iterate the desired
node. the constants “co_node_text”, “co_node_element” and “co_node_cdata_sec2on” help determine
what kind of node type is read.

At this stage the data of the desired node is available in your ABAP system to play with. I am using write
statement below to display the node element name and its corresponding value. Any opera2on can be
performed once the data is brought into your SAP system.
ENDDO
ABAP: How to consume external REST APIs
1
ABAP: How to consume external REST APIs
2
ABAP: How to consume external REST APIs
3
Consuming External API’s in ABAP Code Snippets
Obtaining the relevant details in order to get the Token

In order to get the Token, details like Applica2on ID, Applica2on Secret, Client ID and the Token Endpoint must be provided
by the Provider.

Example how these details looks like is as follows:

Applica2on ID, Client ID and Secret are simply data strings and the Token Endpoint is a URL.

Code Snippet in order to get a Token

The defini2on of which header parameters are needed in order to successfully call the Token Endpoint has to be provided by
the API Provider.

METHOD get_token.
DATA: lo_hfp_token TYPE REF TO if_hfp_client, l_response_token TYPE string.
DATA: l_status_code TYPE i.
DATA: lv_bas64enc TYPE string. DATA: lv_url_token
TYPE string, lv_client_id TYPE string.
DATA: lo_root TYPE REF TO cx_root.

TRY.
*Url to access token lv_url_token = IS_API_CREDENTI-token_endpoint. "Token Endpoint

* create hfp client Object


CALL METHOD cl_hfp_client=>create_by_url EXPORTING
url = lv_url_token

IMPORTING
client = lo_hfp_token EXCEPTIONS
argument_not_found = 1 plugin_not_ac2ve =
2 internal_error = 3 OTHERS = 4.
IF sy-subrc <> 0.
* MESSAGE ID sy-msgid TYPE sy-msgty NUMBER sy-msgno* WITH sy-msgv1 sy-
msgv2 sy-msgv3 sy-msgv4.
*Close the client in case of Error lo_hfp_token-
>close( ). ENDIF.
* Set Method POST
IF lo_hfp_token IS BOUND. lo_hfp_token->request->set_method( if_hfp_en2ty=>co_request_method_po
ENDIF.

*Set header parameter


*Convert string to base64
CONCATENATE IS_API_CREDENTI-APPLICATION_ID ':' IS_API_CREDENTI-APPLICATION_SEC INT

cl_hfp_u2lity=>encode_base64( EXPORTING
unencoded = lv_bas64enc RECEIVING
encoded = lv_bas64enc ).

CONCATENATE 'Basic ' lv_bas64enc INTO lv_bas64enc RESPECTING BLANKS.

lo_hfp_token->request->set_header_field( EXPORTING
name = 'Authoriza2on' value = lv_bas64enc
).

lo_hfp_token->request->set_header_field( EXPORTING
name = 'Content-Type'
value = 'applica2on/x-www-form-urlencoded'
). lo_hfp_token->request-
>set_header_field( EXPORTING name = 'Accept'
value = 'applica2on/json' ). *Set all the form
parameters lo_hfp_token->request->set_form_field(
EXPORTING name = 'grant_type' value = 'client_creden2als'
).

lo_hfp_token->request->set_form_field( EXPORTING
name = 'scope' value = 'client'
).

lv_client_id = IS_API_CREDENTI-client_id. lo_hfp_token-


>request->set_form_field( EXPORTING name = 'client_id'
value = lv_client_id
).

* Turn off logon popup. detect authen2ca2on errors. lo_hfp_token-


>propertytype_logon_popup = 0.

* Send / Receive Token Request


CALL METHOD lo_hfp_token->send EXCEPTIONS
hfp_communica2on_failure = 1 hfp_invalid_state =
2 hfp_processing_failed = 3 hfp_invalid_2meout =
4 OTHERS = 5.
IF sy-subrc <> 0.
* MESSAGE ID sy-msgid TYPE sy-msgty NUMBER sy-msgno* WITH sy-msgv1 sy-
msgv2 sy-msgv3 sy-msgv4. ENDIF.

CALL METHOD lo_hfp_token->receive EXCEPTIONS


hfp_communica2on_failure = 1 hfp_invalid_state =
2 hfp_processing_failed = 3 OTHERS = 4.
IF sy-subrc <> 0.
* MESSAGE ID sy-msgid TYPE sy-msgty NUMBER sy-msgno* WITH sy-msgv1 sy-
msgv2 sy-msgv3 sy-msgv4. ENDIF.

CALL METHOD lo_hfp_token->response->get_status IMPORTING


code = l_status_code.

*Json response needs to be converted to abap readable format


CALL METHOD lo_hfp_token->response->get_cdata RECEIVING
data = l_response_token.

/ui2/cl_json=>deserialize( EXPORTING
json = l_response_token CHANGING
data = is_token "Token will be stored in the instance structur ).

lo_hfp_token->close( ).
CATCH cx_root INTO lo_root.
ENDTRY.
ENDMETHOD.

Data is_token is of the following type

BEGIN OF ts_token, token_type TYPE


string, access_token TYPE string,
expires_in TYPE string, END OF ts_token .

Calling an API with PUT opera2on using the Obtained token including Error Handling

In the following code, An API is called using the Token obtained above in order to write some data on the API Provider side.
Various return status codes which could come as return have also been processed.
Details of the Parameters which needs to be provided in order to make a call has to come from the API provider, Also the
details about the structure of the error data has to come from the API provider.

METHOD put_employee_call. DATA: lv_url_api


TYPE string.
DATA: lo_hfp_put_empl TYPE REF TO if_hfp_client.

DATA: lo_root TYPE REF TO cx_root.

DATA: lv_access_token TYPE string.


DATA: l_response_put TYPE string.

DATA: l_status_code TYPE i.


DATA: ls_mitarbeiter_put_resp_422 TYPE ts_put_resp_unprocessable, ls_mitarbeiter_put_resp_400_4 TYPE
ts_put_resp_400_4.

DATA: ls_errors_422 TYPE ts_put_errors, ls_msg TYPE


bal_s_msg, lv_text TYPE char200, ls_emp_put_resp_200_201 TYPE
ts_put.

TRY.

lv_url_api = is_api_creden2-api_endpoint_new .

*Create hfp client Object


CALL METHOD cl_hfp_client=>create_by_url EXPORTING
url = lv_url_api IMPORTING
client = lo_hfp_put_empl EXCEPTIONS
argument_not_found = 1 plugin_not_ac2ve = 2
internal_error = 3 OTHERS = 4.
IF sy-subrc <> 0.
* MESSAGE ID sy-msgid TYPE sy-msgty NUMBER sy-msgno * WITH sy-msgv1
sy-msgv2 sy-msgv3 sy-msgv4.
*Close the client in case of Error lo_hfp_put_empl-
>close( ). ENDIF.
*Set Method POST
IF lo_hfp_put_empl IS BOUND.
lo_hfp_put_empl->request->set_method( 'PUT' ). ENDIF.

*Set Header fields


lo_hfp_put_empl->request->set_header_field( EXPORTING
name = 'Accept' value = 'applica2on/json'
).

lo_hfp_put_empl->request->set_header_field( EXPORTING
name = 'Content-Type' value = 'applica2on/json'
).
CONCATENATE is_token-token_type is_token-access_token INTO lv_access_tok

lo_hfp_put_empl->request->set_header_field( EXPORTING
name = 'Authoriza2on' value =
lv_access_token
).
*Set body Parameter lo_hfp_put_empl->request->append_cdata( data = iv_empl_json ).
* Turn off logon popup. detect authen2ca2on errors. lo_hfp_put_empl-
>propertytype_logon_popup = 0.

*Send / Receive PUT Request


CALL METHOD lo_hfp_put_empl->send EXCEPTIONS
hfp_communica2on_failure = 1 hfp_invalid_state =
2 hfp_processing_failed = 3 hfp_invalid_2meout =
4 OTHERS = 5.
IF sy-subrc <> 0.
* MESSAGE ID sy-msgid TYPE sy-msgty NUMBER sy-msgno* WITH sy-msgv1
sy-msgv2 sy-msgv3 sy-msgv4. ENDIF.

CALL METHOD lo_hfp_put_empl->receive EXCEPTIONS


hfp_communica2on_failure = 1 hfp_invalid_state =
2 hfp_processing_failed = 3 OTHERS = 4.
IF sy-subrc <> 0.
* MESSAGE ID sy-msgid TYPE sy-msgty NUMBER sy-msgno* WITH sy-msgv1
sy-msgv2 sy-msgv3 sy-msgv4. ENDIF.

*Get status code


CALL METHOD lo_hfp_put_empl->response->get_status IMPORTING
code = l_status_code.

*Obtain Json response


CALL METHOD lo_hfp_put_empl->response->get_cdata RECEIVING
data = l_response_put.

*deserializing json response


CASE l_status_code.
WHEN '422' . " Unprocessable Entry
/ui2/cl_json=>deserialize( EXPORTING
json = l_response_put CHANGING
data = ls_mitarbeiter_put_resp_422
).
*Add log for every Error Entry
LOOP AT ls_mitarbeiter_put_resp_422-errors INTO ls_errors_422. CONCATENATE ls_errors_422-field
ls_errors_422-code ls_errors_422-me me->return_log_variables( EXPORTING iv_text = lv_text CHANGING cs_ms
ls_msg-msgty = 'E'. ls_msg-msgid = const_freetext_msgid. ls_msg-msgno = const_freetext_msgno.
me->add_log_record( is_msg = ls_msg ).
CLEAR: ls_msg,ls_errors_422,lv_text. ENDLOOP.
WHEN '400' OR '404'.
/ui2/cl_json=>deserialize( EXPORTING
json = l_response_put
CHANGING
data = ls_mitarbeiter_put_resp_400_4
).
*Add one log Entry for 400 or 404
CONCATENATE ls_mitarbeiter_put_resp_400_4-message ls_mitarbeiter_put_ me->return_log_variables(
EXPORTING iv_text = lv_text CHANGING cs_msg ls_msg-msgty = 'E'. ls_msg-msgid =
const_freetext_msgid. ls_msg-msgno = const_freetext_msgno. me->add_log_record( is_msg = ls_msg ).
CLEAR: ls_msg,lv_text.

*Add one Success entry for Update


WHEN '200'.
/ui2/cl_json=>deserialize( EXPORTING
json = l_response_put
CHANGING
data = ls_emp_put_resp_200_201 ).

CONCATENATE ls_emp_put_resp_200_201-external_id TEXT-002 INTO lv_tex me->return_log_variables(


EXPORTING iv_text = lv_text CHANGING cs_msg ls_msg-msgty = 'S'. ls_msg-msgid =
const_freetext_msgid. ls_msg-msgno = const_freetext_msgno. me->add_log_record( is_msg = ls_msg ).
CLEAR: ls_msg,lv_text.

*Add one Success entry for Crea2on


WHEN '201'.
/ui2/cl_json=>deserialize( EXPORTING
json = l_response_put
CHANGING
data = ls_emp_put_resp_200_201
).
CONCATENATE ls_emp_put_resp_200_201-external_id TEXT-003 INTO lv_text me->return_log_variables(
EXPORTING iv_text = lv_text CHANGING cs_msg ls_msg-msgty = 'S'. ls_msg-msgid =
const_freetext_msgid. ls_msg-msgno = const_freetext_msgno. me->add_log_record( is_msg = ls_msg ).
CLEAR: ls_msg,lv_text.
ENDCASE.
lo_hfp_put_empl->close( ).
CATCH cx_root INTO lo_root.
ENDTRY.
ENDMETHOD.
Consuming REST APIs with (Cloud) ABAP
API stands for Applica2on Programming Interface, and comprises a set of standards that allow two applica2ons to talk to
each other. REST APIs are a certain pafern of building APIs. They are based on the HTTP protocol, sending and receiving
JSON or XML data through URIs (uniform resource iden2fier). JSON-based REST APIs are prevalent. We will also be using
such one in this tutorial.

OData, which is very popular in the SAP world, is itself a REST API. There is a lot of informa2on out there on how to provide
a REST API from ABAP (i.e., to publish an OData service). But there isn’t much on how to consume an external API in ABAP.
And from what lifle there is, it includes non-whitelisted ABAP APIs, i.e., they cannot be used with Cloud ABAP. So, I decided
to write this tutorial on consuming REST APIs using Cloud ABAP.

Scenario
API Provider
We will be working with JSON Placeholder – a “free to use fake Online REST API for tes2ng and prototyping”. It will allow us
to perform all CRUD (create, read, update, delete) ac2ons. To be fair, the create, update, and delete will not actually work,
but the server will fake it as if they do. Which is completely enough for our use-case!

Resources
Our API provider exposes posts, comments, albums, photos, TODOs, and users. For simplicity’s sake, we will only be using
the posts resource, and pretend the rest aren’t there. The main idea of my tutorial is to provide an extremely simple guide
on how to execute the CRUD ac2ons on a REST API. And do this using whitelisted ABAP APIs in the SAP Cloud PlaVorm (CP).
This means that you can run this code on a SAP CP trial account.

Posts resource
A post has an ID, 2tle, body and user ID, meaning the ID of the user that created the post. We represent it in ABAP as
follows:

TYPES:
BEGIN OF post_s, user_id TYPE i, id TYPE i, 2tle TYPE string,
body TYPE string, END OF post_s, post_f TYPE TABLE OF post_s
WITH EMPTY KEY,

BEGIN OF post_without_id_s, user_id TYPE i,


2tle TYPE string, body TYPE string,
END OF post_without_id_s.
We need the structure without ID because the post ID is automa2cally assigned by the REST API. Meaning that we do not
provide it when crea2ng a new post.

Cloud ABAP APIs used


Sending HTTP requests
As I men2oned earlier, the small number of exis2ng tutorials for consuming REST APIs in ABAP primarily use
nonwhitelisted ABAP APIs. For example, the if_hfp_client one, whose use is not permifed in Cloud ABAP. The way to
check the whitelisted ABAP APIs for the SAP Cloud PlaVorm is to browse the Released Objects lists. It is accessible in
Eclipse ABAP Development Tools (ADT) -> Project Explorer -> Released Objects. So, the cloud-ready ABAP API to send
HTTP request is the if_web_hfp_client. We define the following method to get a client: [defini2on]

METHODS: create_client
IMPORTING url TYPE string
RETURNING VALUE(result) TYPE REF TO if_web_hfp_client RAISING cx_sta2c_check

[implementa2on]

METHOD create_client.
DATA(dest) = cl_hfp_des2na2on_provider=>create_by_url( url ).
result = cl_web_hfp_client_manager=>create_by_hfp_des2na2on( dest ). ENDMETHOD.

No2ce that the URL is an input parameter. The returned result is the created web HTTP client.

Working with JSON


To work with JSON, we will be using the Cloud PlaVorm edi2on of the XCO (Extension Components) library. Read more
about it here and here. The specific class, relevant to our use case is xco_cp_json. Something extremely valuable it provides
is the ability to transform different naming conven2ons. For example, camelCase to under_score, and the other way around.

Consuming the REST API


Before gexng to the fun part, we just have to define a few constants. Of course, this is not strictly necessary, but working
with constants as opposed to string literals is a befer prac2ce, and allows for reusability.

CONSTANTS: base_url TYPE string VALUE 'hfps://jsonplaceholder.typicode.com/posts', content_type TYPE


string VALUE 'Content-type', json_content TYPE string VALUE 'applica2on/json; charset=UTF-8'.

The base URL is simply the address of the posts resource. The lafer two constants we need for the cases where we will be
sending data (i.e., create and update) to the server using the REST API. We have to let the server know we are sending
JSON.
Read all posts
The URL for reading all posts is just the base URL. So, we create a client for it, use the client to execute a GET request, close
the client, and convert the received JSON to a table of posts. The table of posts type is defined in the Posts resource sec2on
above. You can also refer to the full code at the end.

[defini2on]

read_posts
RETURNING VALUE(result) TYPE post_f
RAISING cx_sta2c_check

[implementa2on]

METHOD read_posts.
" Get JSON of all posts
DATA(url) = |{ base_url }|.
DATA(client) = create_client( url ).
DATA(response) = client->execute( if_web_hfp_client=>get )->get_text( ). client->close( ).

" Convert JSON to post table


xco_cp_json=>data->from_string( response )->apply(
VALUE #( ( xco_cp_json=>transforma2on->camel_case_to_underscore ) )
)->write_to( REF #( result ) ). ENDMETHOD.

Read single post


The method to read a single post is similar, with the differences that we take as an input an ID of the post, and return a
structure (i.e., a single post,) instead of a table (i.e., a list of posts). The REST API’s URL of reading a single post is as follows:

hfps://jsonplaceholder.typicode.com/posts/{ID}

[defini2on]

read_single_post
IMPORTING id TYPE i
RETURNING VALUE(result) TYPE post_s
RAISING cx_sta2c_check

[implementa2on]
METHOD read_single_post.
" Get JSON for input post ID DATA(url) = |{ base_url }/{
id }|.
DATA(client) = create_client( url ).
DATA(response) = client->execute( if_web_hfp_client=>get )->get_text( ). client->close( ).
" Convert JSON to post structure
xco_cp_json=>data->from_string( response )->apply(
VALUE #( ( xco_cp_json=>transforma2on->camel_case_to_underscore ) )
)->write_to( REF #( result ) ).
ENDMETHOD.

Create post
As explained earlier, posts’ IDs are automa2cally assigned by the REST API. So, to create a post we will be using the
post_without_id_s type. This will be our input parameter. We are going to convert from this ABAP structure to JSON, once
again using the XCO library. From there, we create a client. Then, we set the body of the HTTP request we are going to send
to be the JSON we just created and we let the server know that we will be sending JSON content-type. Lastly, we execute a
POST request, and return the server’s response. If all went good, the server’s response would return us our post, along with
its newly generated ID (101, because there are currently 100 posts).

[defini2on]

create_post
IMPORTING post_without_id TYPE post_without_id_s
RETURNING VALUE(result) TYPE string
RAISING cx_sta2c_check

[implementa2on]

METHOD create_post.
" Convert input post to JSON
DATA(json_post) = xco_cp_json=>data->from_abap( post_without_id )->apply(
VALUE #( ( xco_cp_json=>transforma2on->underscore_to_camel_case ) ) )->to_str

" Send JSON of post to server and return the response DATA(url) = |{ base_url }|.
DATA(client) = create_client( url ). DATA(req) = client->get_hfp_request( ). req->set_text( json_post ).
req->set_header_field( i_name = content_type i_value = json_content ).

result = client->execute( if_web_hfp_client=>post )->get_text( ). client->close( ).


ENDMETHOD.

Update post
We will be upda2ng with a PUT request. This means we will provide the full post. PATCH, on the other hand, allows us to
only provide the updated field (e.g., only 2tle). If you find this interes2ng, you could try to make the PATCH request yourself
– it shouldn’t be too hard with the provided here resources!
We follow a similar logic as with the create ac2on. We also provide a post as an input parameter, but this 2me we use the
full
structure (with post ID). The URL for upda2ng a post is the same as accessing this (single) post:
hfps://jsonplaceholder.typicode.com/posts/{ID}

So, the only differences from create include the changed type of the post input parameter, the URL, and the HTTP request
method (PUT).

[defini2on]

update_post
IMPORTING post TYPE post_s RETURNING VALUE(result) TYPE string
RAISING cx_sta2c_check

[implementa2on]
METHOD update_post.
" Convert input post to JSON
DATA(json_post) = xco_cp_json=>data->from_abap( post )->apply(
VALUE #( ( xco_cp_json=>transforma2on->underscore_to_camel_case ) ) )->to_str

" Send JSON of post to server and return the response


DATA(url) = |{ base_url }/{ post-id }|.
DATA(client) = create_client( url ). DATA(req) = client->get_hfp_request( ). req->set_text( json_post ).
req->set_header_field( i_name = content_type i_value = json_content ). result = client->execute(
if_web_hfp_client=>put )->get_text( ).

client->close( ). ENDMETHOD.

Delete post
Dele2ng a post is the simplest request. We simply take the ID, and send a DELETE HTTP request to the URL of the specific
post. To let the user if something goes wrong, we check the server’s response code (should be 200 – meaning OK).

[defini2on]

delete_post
IMPORTING id TYPE i
RAISING cx_sta2c_check
[implementa2on]

METHOD delete_post.
DATA(url) = |{ base_url }/{ id }|.
DATA(client) = create_client( url ).
DATA(response) = client->execute( if_web_hfp_client=>delete ).

IF response->get_status( )-code NE 200.


RAISE EXCEPTION TYPE cx_web_hfp_client_error.
ENDIF.
ENDMETHOD.

Tes:ng our code


Now that we have provided all the CRUD func2onali2es, let’s check them out! To do this, we will be implemen2ng
the if_oo_adt_classrun interface, which allows to run a class as a console applica2on. It has a main method that gets
executed – similar to Java.

METHOD if_oo_adt_classrun~main.
TRY.
" Read
DATA(all_posts) = read_posts( ).
DATA(first_post) = read_single_post( 1 ).

" Create
DATA(create_response) = create_post( VALUE #( user_id = 7 2tle = 'Hello, World!' body = ':)' )
).

" Update first_post-user_id = 777.

DATA(update_response) = update_post( first_post ).

" Delete delete_post( 9 ).

" Print results out->write( all_posts ). out-


>write( first_post ). out->write( create_response
). out->write( update_response ).

CATCH cx_root INTO DATA(exc).


out->write( exc->get_text( ) ).
ENDTRY.
ENDMETHOD.
Running with F9 prints the following output:
Beginning of the output in the ABAP console

[…]

End of the output in the ABAP console

Conclusion
This ends the tutorial of how to consume REST APIs in Cloud ABAP. I hope it has been useful for you. If you feel there’s any
points of improvements, or you have any ques2ons or feedback for me, let me know in the comments!
Full code

CLASS zss_tester_2 DEFINITION PUBLIC FINAL CREATE PUBLIC.


PUBLIC SECTION.
INTERFACES: if_oo_adt_classrun.

TYPES:
BEGIN OF post_s, user_id TYPE i, id TYPE i, 2tle TYPE
string, body TYPE string, END OF post_s, post_f TYPE TABLE
OF post_s WITH EMPTY KEY,

BEGIN OF post_without_id_s, user_id


TYPE i, 2tle TYPE string, body TYPE
string, END OF post_without_id_s.

METHODS: create_client
IMPORTING url TYPE string
RETURNING VALUE(result) TYPE REF TO if_web_hfp_client
RAISING cx_sta2c_check,

read_posts
RETURNING VALUE(result) TYPE post_f
RAISING cx_sta2c_check,

read_single_post
IMPORTING id TYPE i
RETURNING VALUE(result) TYPE post_s
RAISING cx_sta2c_check,

create_post
IMPORTING post_without_id TYPE post_without_id_s
RETURNING VALUE(result) TYPE string
RAISING cx_sta2c_check,
METHOD create_client.
DATA(dest) = cl_hfp_des2na2on_provider=>create_by_url( url ).
result = cl_web_hfp_client_manager=>create_by_hfp_des2na2on( dest ). ENDMETHOD.

METHOD read_posts.
" Get JSON of all posts
DATA(url) = |{ base_url }|.
DATA(client) = create_client( url ).
DATA(response) = client->execute( if_web_hfp_client=>get )->get_text( ). client->close( ).

" Convert JSON to post table


xco_cp_json=>data->from_string( response )->apply(
VALUE #( ( xco_cp_json=>transforma2on->camel_case_to_underscore ) )
)->write_to( REF #( result ) ). ENDMETHOD.

METHOD read_single_post.
" Get JSON for input post ID DATA(url) = |{ base_url }/{
id }|.
DATA(client) = create_client( url ).
DATA(response) = client->execute( if_web_hfp_client=>get )->get_text( ). client->close( ).

" Convert JSON to post structure


xco_cp_json=>data->from_string( response )->apply(
VALUE #( ( xco_cp_json=>transforma2on->camel_case_to_underscore ) )
)->write_to( REF #( result ) ). ENDMETHOD.

METHOD create_post.
" Convert input post to JSON
DATA(json_post) = xco_cp_json=>data->from_abap( post_without_id )->apply(
VALUE #( ( xco_cp_json=>transforma2on->underscore_to_camel_case ) ) )->to_s

" Send JSON of post to server and return the response


DATA(url) = |{ base_url }|.
DATA(client) = create_client( url ). DATA(req) = client->get_hfp_request( ). req->set_text( json_post ).
req->set_header_field( i_name = content_type i_value = json_content ).

result = client->execute( if_web_hfp_client=>post )->get_text( ). client->close( ). ENDMETHOD.


METHOD update_post.
" Convert input post to JSON
DATA(json_post) = xco_cp_json=>data->from_abap( post )->apply(
VALUE #( ( xco_cp_json=>transforma2on->underscore_to_camel_case ) ) )->to_s

" Send JSON of post to server and return the response


DATA(url) = |{ base_url }/{ post-id }|.
DATA(client) = create_client( url ). DATA(req) = client->get_hfp_request( ). req->set_text( json_post ).
req->set_header_field( i_name = content_type i_value = json_content ).

result = client->execute( if_web_hfp_client=>put )->get_text( ). client->close( ). ENDMETHOD.

METHOD delete_post.
DATA(url) = |{ base_url }/{ id }|.
DATA(client) = create_client( url ).
DATA(response) = client->execute( if_web_hfp_client=>delete ).

IF response->get_status( )-code NE 200.


RAISE EXCEPTION TYPE cx_web_hfp_client_error. ENDIF.
ENDMETHOD.
ENDCLASS.

External API Integra:on in SAP using REST handlers


– PART 1
This is a step-by-step guide on how to call an external REST service from an SAP system using ABAP code. This
documenta2on can be used as a reference for implemen2ng calls from ABAP to any third party service which supports REST
requests & JSON.The examples in this blog series have been implemented with SAP NetWeaver 7.50. However, the used
ABAP classes exist with NetWeaver 7.3x onwards. Thus, the code can easily be adopted to suit for older SAP releases.

Prepara2ons

Before start coding in ABAP, we must execute the following prepara2on steps.

Export the SSL Cer2ficate from the browser


You need an SSL cer2ficate for the external slack server which hosts REST services. The following steps demonstrate how
you can export the appropriate cer2ficate using Google Chrome.

1. Start Google Chrome and go to url hfps://slack.com/api/conversa2ons.list


2. You will receive hfp status 403 – Forbidden unless your browser already has installed an appropriate cer2ficate. Ignore the
error.
3. Press F12 to start the developer tools.
4. Click on tab Security.
5. Click bufon View cer2ficate.
6. In popup dialog click on tab Details.
7. Click bufon Copy to file in your local machine if you are using Windows.
8. Save base-64 encoded X.509 cer2ficate as file extrestsap.cer which will be imported into SAP.

Install Cer2ficate in the SAP System

Proceed as follows to install the exported SSL cer2ficate in your SAP system.

1. In SAP, call transac2on STRUST.


2. Switch to edit mode (press according tool bar icon).
3. If a local PSE file does not exist already, create it by right-clicking on SSL client SSL Client (Standard) and selec2ng Create
from context menu. Keep all default sexngs in next popup dialog.
4. In Cer2ficate sec2on, click Import (alterna2vely select menu item Cer2ficate → Import). Choose file extrestsap.cer and
import the cer2ficate.
5. Add to cer2ficate list and click save as shown below.
Maintain RFC Des2na2on in SAP

We have to create a RFC des2na2on of type G with the following technical sexngs:

Target Host: hfps://slack.com/api/conversa2ons.list

In our example this RFC des2na2on is called ODATAORG.

Please note that you might have to configure a proxy server if you are in corporate network where access to external sites
and web services are restricted by firewall due to security reasons.

Maintain the des2na2on details based on your requirements. Below screenshot is for demo purpose only.
Configura2on of TLS / SSL parameters in SAP

TLS (Transport Layer Security) is used to secure communica2on between your applica2on program in SAP which acts as a
consumer proxy and the REST API to which we are interfacing with.

There are scenarios where the hosted REST services in the external server support different version of TLS like 1.0 / 1.1 /
1.2. Ensure the required TLS / SSL configura2ons are enabled in SAP to connect with the REST api. Unless otherwise you
may be not able to hit the api from SAP.

In modern api’s TLS version 1.0 is not supported. API integra2ons use TLS 1.1 as a minimum, but version 1.2 is
recommended.

You can test whether your integra2on is compa2ble at any 2me using the test environment (hfps://api-
testbed.giŠbit.com/papi/v1)

If your test calls fail with an SSL handshake failure or similar error in SM59 when tes2ng the connec2on, refer the F1
documenta2on or ICM Monitor ( TCode: SMICM ) for you to communicate with rest API. This type of failure may be caused
by an outdated language version or library being used that does not have support for newer TLS versions.

Afached OSS message discusses in detail about the SSL configura2ons to be enabled in the ABAP applica2on server.
hfps://launchpad.support.sap.com/#/notes/510007

Thats all about configura2ons to be maintained. In the next part of this blog series, we will see how to write the ABAP code
solu2on to consume these REST api’s using the standard handler CL_REST_HTTP_CLIENT and parser class /UI2/CL_JSON.
Example:

Setup E-invoice throught the concepts men2oned in the above ar2cle.

1. You are impor2ng the cer2ficate from google chrome and adding to the server – Do we need to do the same
ac2vity.

It depends on the user case in my case using Slack / Intercom was sufficient.

2. Crea2ng a RFC to the API. – We want to access the


hfps://einvapisandbox.nic.in/einvapiclient/EncDesc/ApiEndsPoint.aspx – How to create this one

Please go through this blog series to get deeper understanding of how to achieve the above requirements. We
have long / strict set of configura2ons involved that are discussed in the blogs.

3. Our connec2on is through HTTPS.

Yes, this is supported provided STRUST setup is enabled for the SAP system to act like client proxy.

Technical implementa2on:

In this sec2on, all steps are described that you must implement for calling an external SLACK based web service from ABAP.
First we send a GET request to the external REST api and then receive the response data in JSON format. AŠerwards this
JSON is converted into na2ve ABAP format.

Following are the ABAP classes are used.

CL_HTTP_CLIENT

CL_REST_HTTP_CLIENT

/UI2/CL_JSON

Step 1:

Create a new REST client object with necessary request headers and authoriza2on key.

Des2na2on details should be maintained and received from SM59.


METHOD create_hfp_client.
*& This method is used to fetch the des2na2on details about the SLACK Server fro *-------------------------------------------------
--------------------------*
* Data Declara2ons*
*---------------------------------------------------------------------------* DATA:lv_reason TYPE string,
lv_utc_2mestamp TYPE 2mestampl.

DATA gv_auth_val TYPE string.

CONSTANTS gc_auth TYPE string VALUE 'Authoriza2on' ##NO_TEXT.


CONSTANTS gc_content_type TYPE string VALUE 'content-type'.
CONSTANTS gc_accept TYPE string VALUE 'ACCEPT' ##NO_TEXT.
CONSTANTS gc_accept_value TYPE string VALUE 'applica2on/json'.
CONSTANTS gc_rfc_dest TYPE rfcdest VALUE 'SLACK_REST_API_CONV' ##NO_TEXT. CONSTANTS gc_tabname TYPE
rstable-tabname VALUE 'Z_SLACK_LOG'."Table Name CONSTANTS: gc_e TYPE dd26e-enqmode VALUE 'E'.

*Create the HTTP client instance


CALL METHOD cl_hfp_client=>create_by_des2na2on EXPORTING
des2na2on = i_rfc_dest IMPORTING
client = go_hfp_client EXCEPTIONS
des2na2on_not_found = 1 internal_error =2
argument_not_found =3 des2na2on_no_authority
=4 plugin_not_ac2ve =5 OTHERS = 5.
IF sy-subrc NE 0.
"Log the Excep2on
*Ping Des2na2on Failed get 2me stamp field lv_utc_2mestamp.
lv_reason = sy-msgv1.
RETURN. ENDIF.

CHECK go_hfp_client IS BOUND.


*Set the HTTP header fields
CALL METHOD go_hfp_client->request->set_header_field EXPORTING name
= gc_auth value = gv_auth_val.
CALL METHOD go_hfp_client->request->set_header_field EXPORTING name
= gc_accept value = gc_accept_value.
CALL METHOD go_hfp_client->request->set_header_field EXPORTING
name = gc_content_type value = gc_accept_value. " set hfp
protocol version go_hfp_client->request->set_version(
if_hfp_request=>co_protocol_version_1_0 ) .
ENDMETHOD.

Step 2:

REST call – HTTP GET

We get a string variable that contains the response data in JSON format. To process the data in ABAP, it must be converted
into an appropriate ABAP structure or data object for which we use standard parser class

If the request is successful, convert the JSON string into ABAP na2ve internal table format using standard parser class
/ui2/cl_json which contains methods to perform serializa2on and de-serializa2on. Please read here to know more about
these opera2ons.

Catch here is, we need to create a local type or a global type with the afribute names as same as JSON response retrieve
from the REST api.

Con2nua2on of Report code…


REPORT z_fetch_slack_data.

*Decla2ons to store the na2ve format data


TYPES: BEGIN OF ts_slack_info, user TYPE string, email
TYPE string,
END OF ts_slack_info .

DATA ls_slack_data TYPE ts_slack_info.


DATA go_hfp_client TYPE REF TO if_hfp_client . DATA: lr_json TYPE REF TO /ui2/cl_json.

CHECK go_hfp_client IS BOUND.


*Set the User ID
IF c_user_id IS NOT INITIAL.
lv_user_guid = c_user_id-zuser_guid. cl_hfp_u2lity=>set_request_uri(
EXPORTING request = go_hfp_client->request uri = lv_user_guid ).

*Create REST Client object


CREATE OBJECT lo_rest_client
EXPORTING io_hfp_client = go_hfp_client.
TRY.
lo_rest_client->if_rest_client~get( ).
DATA(lo_response) = lo_rest_client->if_rest_client~get_response_en2ty( ). DATA(lv_hfp_status) = lo_response-
>get_header_field( '~status_code' ).
IF lv_hfp_status NE 200. lv_error_status = lv_hfp_status.

*HTTP Request Failed


DATA(lv_reason) = lo_response->get_header_field( '~status_reason' ).

*STOP Processing e_return-type = gc_err.


e_return-message = lv_reason.
RETURN. ENDIF.

*Receive the response data in JSON.


DATA(lv_json_data) = lo_response->get_string_data( ).
"Refresh the SLACK response to clear HTTP memory of previous calls
IF go_hfp_client IS BOUND.
go_hfp_client->refresh_response( ).
"Close HTTP session (Excep2on HTTP_NO_MEMORY) go_hfp_client-
>close( ). ENDIF.

*Collect into the excep2on table.


CATCH cx_rest_client_excep2on INTO DATA(lo_rest_client_excep2on). ENDTRY.

IF lv_json_data IS NOT INITIAL.


CREATE OBJECT lr_json.
TRY . lr_json->deserialize_int( EXPORTING json = lv_json_data CHANGING data = ls

ENDIF.

CATCH cx_sy_move_cast_error INTO DATA(lo_move_cast_error) .

DATA(lv_msg_desrl_err) = `HTTP GET failed: ` && lo_move_cast_error->get_long


ENDTRY.
ENDIF.
Step 3: Troubleshoo2ng Following are some of the errors that could occur. Part 1 of this blog covers the configura2ons
required to circumvent most of the below errors.

GET or POST method throws excep2on Communica2on Error

Verify that the specified host name is correct.


Check proxy server sexngs.
HTTP status is 400 (Bad Request)
Check uri path and parameter string.

HTTP status is 401 (Unauthorized)

Check that user name and password as specified in your program matches those of your service instance in external
server.

HTTP status is 403 (Forbidden)

Proceed as follows to check if the SSL cer2ficate for the external server is installed correctly.

1. Call transac2on SMICM.


2. Select menu item Goto → Trace File → Display End
3. If you find the following messages, re-install the SSL cer2ficate.

Disclaimer: Above ABAP code snippets are for illustra2on purposes only and these are untested. Please tailor/validate these
codes based on your business requirements for the produc2on purposes.

You might also like