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

In two recent blogs, I demonstrated how to write web clients of REST APIs - with XML (demo application here) or JSON (demo application 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 implementing the interface IF_HTTP_EXTENSION, which consists of one single method HANDLE_REQUEST. A request handler class can be attached to a path in transaction SICF. An incoming HTTP request will be analyzed by the Internet Communication Framework, trying to match the request path against the SICF path. The match process is stopped as soon as a node with an attached 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 attached to the path /job/attributes. The class ZCL_JOB_DATA is declared to be responsible for all incoming requests where the request path starts with /job/attributes :

First Strategy: HTTP Request Method

The implementation of the interface method if_http_extension~handle_request() forms the uppermost level of processing. Therefore, the implementation only gives the rough processing skeleton: An instance for database operations, as well as an instance for the processing of the REST operation 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 situation should result in an HTTP response with status code '400 - Bad Request'.

At this place, we are using the Strategy design pattern: 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_http_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 operations
       lo_db   ?= get_db( io_server = server ).

* Get the correct rest handler instance, depending on the verb (GET, PUT, POST, OPTIONS, DELETE)
       lo_rest ?= get_rest( io_server    = server
                                    io_db        = lo_db   ).

* Do the operation
       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 convention for the instance determination: 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 creation. Alternatively, we could have written a large CASE ... statement with many WHEN's. The advantage of the CASE would be that the create object statement could be statically checked for syntactical 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)
         exporting
           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 initial. 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 application of the Strategy pattern.


class lcl_converter definition abstract.
   public section.
     class-methods get_instance
       importing
         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
       importing
         iv_cdata type string
       exporting
         es_job type zjobs
       raising zcx_parse_error.
     methods result_to_cdata abstract
       importing
         is_result type ty_result
       exporting
         ev_cdata type string.
endclass.                    "lcl_converter DEFINITION

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

class lcl_converter implementation.
   method get_instance.
     if iv_accept cs 'application/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, implementing 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 time in lif_rest~handle_request(), an abstract method do( ), which has to be redefined in the subclasses. This do() method will contain the specific action.

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

  1. Execute the specific action by calling do(),
  2. Error handling, with HTTP error code 400 "Bad Request" in case of conversion error (wrong incoming data), or setting response data for an error message in case of an application 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: application/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 operation
         do( importing 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, respectively
     call method go_converter->result_to_cdata
       exporting
         is_result = ls_result
       importing
         ev_cdata  = lv_cdata.

* Place the result in the response body
     call method set_response
       exporting
         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 illustration: The PUT request - which always is a task to update or insert job attributes 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 implementation, no instances could be built.):

class lcl_rest_put definition inheriting from lcl_rest.
   protected section.
     methods do redefinition.
endclass.                    "lcl_rest_put DEFINITION

The implementation 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 implementation.
   method do.
     data: ls_job type zjobs,
           lv_id  type zjobs-id.
     try.
         get_job_by_id( importing es_job     = ls_job ).
         lv_id = ls_job-id.
       catch zcx_not_found.
     endtry.

     clear ls_job.
     call method go_converter->get_entered_data
       exporting
         iv_cdata = go_request->get_cdata( )
       importing
         es_job   = ls_job.

     if ls_job is not initial.
       if lv_id is not initial.
         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 implementation 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, Identity and Locking

In the test applications (neither in the JSON app nor in the XML app), there is neither login nor enqueue of the data. Since the applications are open for everybody, this works only since I don't really operate on a database table ZJOBS. Actually, each client who calls the application is working with his own session data, so he doesn't conflict with other users' operations, 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 written as BSP, there is a session-ID available in the attribute runtime->server_id. This session ID identifies the particular browser instance that made the request. On the client-side, this session-ID is always contained in a cookie called sap-appcontext. If an application 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 function which extracts the sap-appcontext from the cookie:


function get_appcontext() {
  var lAppcontextCookie = document.cookie.match(/sap-appcontext=(.*?)(?:;|$)/);
  return lAppcontextCookie &&
         ( lAppcontextCookie.length >= 2) &&
         unescape( lAppcontextCookie[1] ) || "";
  }

The appcontext returned from this function, 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 initial.

* Base64 decode
     lv_app_context   = cl_http_utility=>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 initial.
     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 essential 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 authenticates, his browser receives a session-id with a limited validity. That session-ID has to be passed with each successive REST operation. 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 pretty straightforward to implement - calling an XSLT transformation 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 transformations. This is possible sincehorst.keller/blog/2013/01/07/abap-and-json 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 confirmations",
  "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 representation like this one (the format is easy to understand - I think a detailled explanation is not necessary here):


<?xml version="1.0" encoding="utf-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 confirmations</str>
<str name="CONTACT">Rainer Zufall</str>
</object>

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


<xsl:transform version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
  <xsl:template match="/">
    <asx:abap xmlns:asx="http://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="http://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 confirmations</DESCR>
    </JOB>
  </asx:values>
</asx:abap>

This is a valid ABAP data description. If the transformation 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 implementation of the JSON converter.

class lcl_json_converter implementation.
   method get_entered_data.

     data: lo_ex type ref to cx_transformation_error.

     clear es_job.
     check iv_cdata cn space.

     try.

         call transformation zjson2job
           source xml iv_cdata
           result job = es_job.

       catch cx_transformation_error into lo_ex.
         raise_parse_error( lo_ex ).

     endtry.

   endmethod.                    "get_entered_data

Many things can be done with the identity transformation ID, with no need to define an own XSLT transformation at all. If you can impose the JSON data structure to be used in the web application, it is of advantage to use such a "canonical" structure. For example, consider wrapping the JSON hash with the job attributes 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 confirmations",
    "CONTACT": "Rainer Zufall",
    "ID": "0001"
  }
}

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


call transformation id
  source xml iv_cdata
  result job = es_job.

In this example, since I have written 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 representations of ABAP data:

  • A JSON format may be designed in favour of the web application - to optimize the readability of the client JavaScript code working on the data.
  • There may be client components requiring a particular JSON formats. For example, the jQuery datatable requires the table data to be passed as an array of arrays: http://www.datatables.net/release-datatables/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 essential 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 mentioned, the result data structure contains

  • a message,
  • a message type,
  • and a table of job attributes:

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 identity transformation, 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 representing 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 confirmations"
    },
    "0002": {
      "REPID": "RBDAPP01",
      "VARID": "UXPD_EDI_GUT02",
      "PRIO": "3",
      "RESTART": "X",
      "CONTACT": "Herbert Hurtig",
      "DESCR": "Credit Memos"
    }
  },
  "MESSAGE": "",
  "TYPE": ""
}

We proceed in a similar way as above, only in the other direction: based on the ABAP data type ty_result,  we write an XSLT transformation 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="utf-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 confirmations</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 Hurtig</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 transformation. On the other hand, the asXML format of the structure ty_result looks like this:


<?xml version="1.0" encoding="utf-8"?>
<asx:abap xmlns:asx="http://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 confirmations</DESCR>
    </ZJOBS>
    <ZJOBS>
     <ID>0002</ID>
     <REPID>RBDAPP01</REPID>
     <VARID>UXPD_EDI_GUT02</VARID>
     <PRIO>3</PRIO>
     <RESTART>X</RESTART>
     <CONTACT>Herbert Hurtig</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 transformation:


<xsl:transform xmlns:xsl="http://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 deviation from the "canonical" JSON representation of ABAP data, there is a template in the XSLT transformation handling this deviation. 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 attribute of the ZJOBS data structure, it has to be raised one level higher to become the key of a hash. All the other attributes, 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 transformation 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 implementation of REST APIs in ABAP. It is possible to keep separate concerns in separate (local or global) classes by applying patterns 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):

48 Comments
Former Member
0 Kudos

Such a good post! I had a requirement to expose a REST API about 16 months ago.  This would've come in very helpful.  Grade A development!

rdiger_plantiko2
Active Contributor
0 Kudos

Hello Craig, thank you for the good grade! :smile: Maybe the blog gives you some ideas for your next REST API project! Cheers, Rüdiger

cesar_at_sap
Advisor
Advisor
0 Kudos

Hi Rüdiger,

Very nice!! I hadn't read your post until now, which is a nice coincidence that we have been working in very similar directions. Check my post here:

http://scn.sap.com/community/abap/connectivity/blog/2013/03/05/json-adapter-for-abap-function-module...

Looks that we all have the same concerns these days!

Cheers,

        César.

Former Member
0 Kudos

Hi Rüdiger,

This info helped me greatly to understand the concept of Rest API.

The problem is i created a service in sicf with handler class which implements IF_HTTP_EXTENSION~HANDLE_REQUEST, but when i execute the corresponding html page it is not triggering the handler class :sad: ..

Please Help me

Thanks in advance,

Sreeja.

rdiger_plantiko2
Active Contributor
0 Kudos

Hi Sreeja,

"Please Help me"

I would like to...

"when i execute the corresponding html page it is not triggering the handler class :sad: .."

Assuming you know that a REST service is not an HTML page but only a remotely callable function using HTTP as protocol and with data as response (usually in XML or JSON format), I understand that you have a web application which calls the REST API implicitly with JavaScript.

When you analyze the page behaviour with a tool like Firebug:

  • is the request really issued by your JavaScript? You see this in your Network tab
  • is there a response sent from the SAP system?
  • Is this response OK (status 200), or not OK (status 40x or even 50x)? In the network tab, the latter are written in red color.
  • If the response is not OK, analyze the response for finding out what went wrong.

There is a variety of possible reasons that a request may fail: Wrong URL, SICF service not active, wrong or missing request data (when required), an ABAP short dump during processing, ...

Regards,

Rüdiger

Former Member
0 Kudos

Hi Rudiger,

I am connecting to SAP using HTTPWebResponse and HTTPWebRequest. We are fetching a list of orders from SAP.   The issue is that for each order that is fetched from SAP, a session gets created and does not get closed/end.  On debugging we used the URL used to fetch the data (created in the code) and pasted in the browser window. It created a session. Now we used the URL (created in the code) for the close session  and pasted in the browser window. It logged off the session. Ultimately from the browser we are able to open and close session but not from the code. Can you please help?

rdiger_plantiko2
Active Contributor
0 Kudos

Hi Jane, if you only want a list of orders, you don't need a stateful service. Working with a stateless service instead, would mean that the session will not be preserved.

Regards, Rüdiger

former_member191562
Participant
0 Kudos

Hi Rüdiger,

Great post, I am glad that I ran into your blog.


I have been reading and trying to implement REST API in ABAP and communicate with it from an Android device (Out of curiosity mostly). So far I have succeeded doing it by defining a class implementing IF_HTTP_EXTENSION~HANDLE_REQUEST( ).

Your idea of implementing Strategy pattern to handle PUT, GET etc is cool. I was happy that I could do the same untill I ran into this help documentation REST Interfaces and Classes - Connectivity - SAP Library. The new REST library already has interfaces to implement the PUT, GET requests instead of doing it via our logic using '~request method' header value.

But I am not very clear on how to achieve this since I couldn't get my hands on the REST library as its not yet available in the system that I am working on.

It will be very useful if we get to see some documented implementation.

cesar_at_sap
Advisor
Advisor
0 Kudos

Hi Yuvaraj,

The ABAP REST Library is quite new and it is very likely that your system doesn't have it yet. It is coming in the basis system with release 7.40 and you can only get it in previous releases only with the latest SAP_BASIS patches.

It is only documented for 7.40. You already have the correct link. You can also find this blog useful: http://scn.sap.com/community/abap/connectivity/blog/2013/05/16/usage-of-the-abap-rest-library-sapbas...

I think that this ABAP Library is the end to all of our separate efforts to do REST like interfaces for ABAP. Probably this is the way to go from now on.

Hope this helps,

    César.

rdiger_plantiko2
Active Contributor
0 Kudos

Hi Yuvaraj,

thanks for pointing me to the SAP REST library which has been delivered with basis release 731 and is absolutely new to me. 

As with every framework, it may be that it delivers too much for a concrete purpose. I will have to explore that.

If you have questions about my way of implementation - as exposed here -  then go ahead. What I cannot do is answer questions about the SAP REST library, since it is new to me. (But you don't have a concrete question about it, either. Only on the availability: which seems to be SAP_BASIS 731)

Cheers,

Rüdiger

former_member191562
Participant
0 Kudos

HI Caesar,

Yes, the REST Library is new. I was interested to see how the classes will work. A demo application would have given a good perspective.

I was able to take a look at the classes in a Sample System, Sign up and get started with the SAP Gateway - Demo Consumption System, which is mainly available to connect and consume pre-loaded webservices.

The classes are defined with the name space, '/IWCOR/' (eg./IWCOR/CL_REST_HTTP_HANDLER). But even in that system I was not able to find any Service defined using the REST Library, also there is no developer access hence it was not of much use. But it could be helpful if we need to know how SAP has handled REST communications.

cesar_at_sap
Advisor
Advisor
0 Kudos

Hi Rüdiger,

Let me confirm availability: the REST Library is included in 7.40 and 7.31/7.03.

A specific release is also included as part of SAP NetWeaver Gateway, but intended to be used only for Gateway, not as a general solution. It is used internally by Gateway.

The REST Library project started internally at SAP at least in 2010, but has only been recently released. It is fully documented only for 7.40.

I haven't had the time to play with it either, but it looks promising. Let's keep watching.

Best regards,

           César.

cesar_at_sap
Advisor
Advisor
0 Kudos

Hi Yuvaraj,

The system you're accessing is currently a release 7.02. As such, the SREST package is missing.

The classes you're mentioning do indeed belong to the REST Library, but it is the implementation specific to be used by Gateway. This implementation is in principle not intended to be used generically. So I'm afraid you cannot do much with it. You need a 7.03/7.31 or 7.40 system.

Regards,

      César.

Former Member
0 Kudos

Hi Rüdiger,

I have a situation where I need to autmatically login to SAP when the user enter id and pwd into a .net application. I am planning to create a rest service through SICF node and redirect the user to a SAP web dynpro application.

My biggest challenge is

How can I authenticate him, I am planning to match the user id and pwd of .Net application with that of SAP, so when the call the rest using the URL I dont want a Pop up for user id and passwod

Is there a way to avoid the popup requesting userid and password since I already have the user id and password and can pass as URL param. there is no security concerns as it a terminal server.

Thanks for any suggestion or help.

Raghavendra Prabhu

rdiger_plantiko2
Active Contributor
0 Kudos

Hi Raghavendra,

unfortunately, I don't understand your scenario.

So you have a .Net application and want to present the user a web dynpro from there. But for what do you need REST then.

"Is there a way to avoid the popup requesting userid and password since I already have the user id and password and can pass as URL param. there is no security concerns as it a terminal server."


This seems to be somehow unrelated to the former. If it bothers you that your service requires user and password, you could make the service anonymous by providing credentials in the SICF service.


If you really want to pass userid and password as URL parameters - which nobody would recommend you, even in a seemingly protected environment - you can afford this by using the appropriate URL query parameters: sap-user and sap-password.


Just read the docu


http://help.sap.com/saphelp_470/helpdata/en/6f/04d73a90e7322be10000000a11405a/content.htm


for further infos.


Regards,

Rüdiger



Former Member
0 Kudos

Hi Rüdiger
Really appreciate your reply, we have a scenario where in single sign on is not a possibility.

I have a third party application through which the user enter the credentials. Basically I want to call a Webdynpro screen after the user logins through third party, we are planning to sync the user id of the third party application with that of SAP. After user logins through third party application I want to call a service through SICF node and login without giving the prompt to enter user id and pwd again, after the login in is successful I shall redirect from the service handler to the web dynpro application. Sorry if I am confusing you.

I want to skip that password prompt which SAP gives when we call the SICF service by specifying the URL.

Thanks very much for all your inputs.

Raghavendra Prabhu


rdiger_plantiko2
Active Contributor
0 Kudos

Hi Rhagavendra,

so this part of my former reply should be  the answer:


If it bothers you that your service requires user and password, you could make the service anonymous by providing credentials in the SICF service.



If you really want to pass userid and password as URL parameters - which nobody would recommend you, even in a seemingly protected environment - you can afford this by using the appropriate URL query parameters: sap-user and sap-password.




Just read the docu




http://help.sap.com/saphelp_470/helpdata/en/6f/04d73a90e7322be10000000a11405a/content.htm




for further infos.


Kind regards,

Rüdiger

carsten_ziegler
Active Contributor
0 Kudos

Rüdiger, you made my day!

Just looking into http client and server handling on ABAP for the communication between ABAP and HANA XS. This blog post saves me some time. :smile:

Once again I have to thank you for your work.

rdiger_plantiko2
Active Contributor
0 Kudos

Carsten, I enjoy it that the blog was of use for you. Thanks for your feedback! Regards, Rüdiger

rdiger_plantiko2
Active Contributor
0 Kudos

You might also be interested in the ABAP REST library (which I didn't know when I had written this blog).  Some of the abstractions which I detailed in this blog seem to be available there for reuse:

Usage of the ABAP REST Library [SAP_BASIS 7.40]

carsten_ziegler
Active Contributor
0 Kudos

Thanks for the link. Diving into the topic at the moment...

Former Member
0 Kudos

hello sir ,

i have to consume json format webservice in abap . i consumed and now the format in

json-xml as i attached  i used  SMUM_XML_PARSE but the cvalue and cname both are comming in cvalue. please tell me how to get the data

0 Kudos

Thank you very much Rüdiger!

Additional question: how is it possible to handle locks? I want to change e.g. a notification in a web application, but if i want to lock it in sap (with enqueue) the lock will be deleted after the request is finished.

Regards Vedran

rdiger_plantiko2
Active Contributor
0 Kudos

Hi Vedran,

in the stateless request that I prefer, there is no other way than to write a lock entry in a special database entry. In the applications in which we needed such a locking, we had created a special database table ZLOCK, with a GUID as key, then in the data part a generic object key field, an object type (together building a secondary index), a field for user-id, and the lock creation time. Requesting a lock looks for entries in ZLOCK for the same object key & type, and with a creation date not older than a system-wide defined timeout parameter, and with a different user-id than the own one. If such an entry exists, the current user can set his lock, the exception is thrown to inform the service that the object in question can't be changed. If no such entry exists, the user can set his own entry. If an entry exists but is out of date, user and creation time can be updated.

Additional to the timeout mechanism, you can provide a special logoff function which clears all entries for the current user.

Similar to SM12 for normal enqueue entries, a little report is helpful for support to show the current locks, and to delete them (in special cases).

Hope this helps

Rüdiger 

0 Kudos

Hi Rüdiger

Thanks for your answer. I had also the same idea, but it's a lot to do to develop an extension in or around the enqueue-FMs code to hande this. So I changed to stateful requests with the method server->set_session_statefeful( ) inside the handler. I hope that no big disatvantages will appear.

Greets

Vedran

rdiger_plantiko2
Active Contributor
0 Kudos

Hi Vedran,

but it's a lot to do to develop an extension in or around the enqueue-FMs code to hande this.

a central service class for enqueue requests which works against a DB table could be worth  writing it. For our projects it definitely was. By the way, we didn't touch the standard enqueue-FM's.

The big advantage of "stateless" applications is that they scale much better. 

Regards,

Rüdiger

0 Kudos

Hi Rüdiger,

It's me again. Now I wrote a stateful application (outside sap, php on a web server). It works great with standard locking mechanism in sap. I can remove locks with the same session and if I logout, the locks are also removed! I can read an manupulate data, Perfect!

But... If I make a change at the source code (sap side), the current session doesn't recognize the changes. This isn't a big thing, I could kick the user/session from SM05.

Now my problem: if I change e.g. an equipment in SAP, the changes also aren't recognized at the current session (if I've already open this equipment from web).

Do you know why this is happening? Is there a setting for it?

I though why not using SAP standart stuff... now I regret why I didn't implement your suggestion...

Thank you.

Greets Vedran

rdiger_plantiko2
Active Contributor
0 Kudos

Hi Vedran,

what you describe is standard SAP behaviour. The needed data are loaded into the session memory from data base when a transaction is started, and later changes of the database won't effect the session data in memory. This is for data consistency during a session.

BTW, you would face similar problems if you kept session data in table SSCOOKIE for usage across dialog steps, when working with stateless applications.

In both cases, you would need to have some kind of notification mechanism to get your transaction informed about changes.

For this, you would need a central entry point in the code which is passed with each dialog step. For BSP applications, this would be something like the method DO_INITATTRIBUTES or even DO_REQUEST (redefined). In this, you could check something like a "change stack table" for recent changes and reload the master data if necessary. You won't get this for free!

But inspite of this being technically possible, the question remains:

What if the user's changes make sense only for the version of data as of when the user started the transaction? How to handle this?

Regards,

Rüdiger

Former Member
0 Kudos

Hi Rüdiger,

This blog gave me some understanding of configuring service in SICF and implementing the interface, IF_HTTP_EXTENSION~HANDLE_REQUEST. But, I need to access the URL used to access this service. I need to read or access the parameters concatinated to the URL generated by SICF. Because, to the URL generated by SAP, users will input and pass some default values to the selection screen of a transaction code. I need to capture those paratmeters and perform some operation on the same. Please help.

In addition, with the implementation of custom class, parameters passed in GUI configuration of SICF have become irrelevant. Though I am passing singletransaction parameter, it is not showing any effect as we are using custom handler class. Any suggestions on how to get this work?

Thanks,

Phani

rdiger_plantiko2
Active Contributor
0 Kudos

Hi Phani,

you want to get form field values (passed as query parameters in the URL)? Then use server->request->get_form_field() and RTFM for further questions on the call details.

For this, you wouldn't need the URL. You can use server->request->get_form_field( ), it retrieves the URL query parameters as parsed from the URL, so you don't need to parse the URL yourself. If, for whatever reasons, you would like to do this parsing yourself, you can use the pseudo header field ~query_string (that is, you call server->request->get_header_field( '~query_string' ) ). See List of the Pseudo Header Fields - Components of SAP Communication Technology - SAP Library for more details.

As for the second question: I must admit I don't understand it. Maybe somebody else does.

Regards

Rüdiger

Former Member
0 Kudos

Hi Rüdiger,

Thanks for your quick response.

Regarding the second question, we have the option to set parameters of the service. As shown below, I have selected the parameter ~singletransaction as 1. This will not allow user to navigate to any other transaction apart from the one linked with the respective URL. As I have used custom implementation of handle request, this is not working anymore.

Article - Employ SAP GUI for HTML instead of an Internet service - Wiki - SCN Wiki

Also output displayed is truncated. I mean output displayed is not complete. But if I just scroll little bit towards right, then the page is getting refreshed and I can see the complete output.

Thanks,

Phani

former_member202108
Discoverer
0 Kudos

Hi Colleagues,

Is it Async Request possible here in REST API?

If yes, could you please tell me how to achieve it.

Thanks in advance.

Regards

Vasanth

arijitkumardas
Product and Topic Expert
Product and Topic Expert
0 Kudos

Hi rdiger.plantiko2

A truly wonderful blog! It makes for very interesting reading.

I have a question - would you be familiar with how SAP ECC (or any SAP backend system) can call an apigee end-point for a synchronous scenario?

The integration pattern is - ECC - apigee - app - apigee - ECC

I understand that a client_id and client_secret are required by apigee (POSTMAN screen shot attached below).

Regards

Arijit

rdiger_plantiko2
Active Contributor
0 Kudos

Hi Arijit, your question is slightly off-topic, as this blog describes the SAP system in the server role. You are asking on how to use a SAP system as client to perform API calls via the unified tool apigee.

Anyway - if you know the form fields to submit, you can use class cl_http_client for this. It is documented, and there are sample reports like RSHTTP01.

Regards,

Rüdiger

arijitkumardas
Product and Topic Expert
Product and Topic Expert
0 Kudos

Thank you rdiger.plantiko2 for your help in pointing me in the right direction. I shall try the report 🙂

former_member202213
Participant
0 Kudos

Hi Rüdiger,

thanks a lot for your precise description.

I'm trying to use your example in my system. I searched in the code you provided.

the direction of transcodification I'm trying to do is the following :

internal table in SAP -> XML -> REST.

my sap basis version is 7.2. I haven't got the rest object in my system.

I just applied the oss 1648418 in order to use type json likeif_sxml=>co_xt_json.

despite my efforts, I can't manage to use the transormation you use with ls_result as entry :

lo_writer = cl_sxml_string_writer=>create( type = if_sxml=>co_xt_json ).
   CALL TRANSFORMATION zjpar_test04
*  SOURCE data = lt_itab
     SOURCE data = ls_result
     RESULT XML lo_writer.
   ev_cdata = cl_abap_codepage=>convert_fromlo_writer->get_output( ) ).


sorry if my question seems obvious but how do you use ls_result with your following structure :


TYPES:
    BEGIN OF ty_result,
      msgtype TYPE symsgty,
      message TYPE c LENGTH 255,
      jobs TYPE string,
    END OF ty_result.
I try with the following data but can't figure out the format I need to use :

  1. <?xml version="1.0" encoding="utf-8"?> 
  2. <asx:abap xmlns:asx="http://www.sap.com/abapxml" version="1.0"> 
  3. <asx:values> 
  4.   <DATA> 
  5.    <JOBS> 
  6.     <ZJOBS> 
  7.      <ID>0001</ID> 
  8.      <REPID>RSNAST00</REPID> 
  9.      <VARID>UXPD_KUBE_KV</VARID> 
  10.      <PRIO>2</PRIO> 
  11.      <RESTART>X</RESTART> 
  12.      <CONTACT>Rainer Zufall</CONTACT> 
  13.      <DESCR>Output all sales order confirmations</DESCR> 
  14.     </ZJOBS> 
  15.     <ZJOBS> 
  16.      <ID>0002</ID> 
  17.      <REPID>RBDAPP01</REPID> 
  18.      <VARID>UXPD_EDI_GUT02</VARID> 
  19.      <PRIO>3</PRIO> 
  20.      <RESTART>X</RESTART> 
  21.      <CONTACT>Herbert Hurtig</CONTACT> 
  22.      <DESCR>Credit Memos</DESCR> 
  23.     </ZJOBS> 
  24.    </JOBS> 
  25.    <MESSAGE>Test</MESSAGE> 
  26.    <MSGTYPE>I</MSGTYPE> 
  27.   </DATA> 
  28. </asx:values> 
  29. </asx:abap> 

thanks for your help.

regards,

Jean-François.

rdiger_plantiko2
Active Contributor
0 Kudos

Hi Jean-François, from your question it is not clear which problem you have. The piece of code that you pasted seems to indicate that you

  • have a self-written transformation zjpar_test04 (which we don't know),
  • you want to apply this transformation to a structure, not an internal table, since you use a variable named ls_result
  • and you want to receive the result in the form of JSON data.

The result of the transformation must therefore be a valid XML document, conforming to the XML-JSON syntax. You can check this by using my schematron validator that I described in another blog. Just write the result not into lo_writer but into a string ( "result xml lv_result" ), copy the XML from the debugger at that point, and paste into the JSON-XML validator.

If it is valid, then pretty sure lo_writer will receive correct JSON in your code snippet. If not, you'll have to adapt the transformation to make it work.

Hope this helps,

Rüdiger

former_member202213
Participant
0 Kudos

Hi Rüdiger,

thanks a lot for your help concerning my issue.

I know that my problem is not clear as I'm improving my skills concerning APIs while developping.

thanks to you, i succeeded in developping the transformation sheet and converting XML to JSON.

contrary to your solution, i'm not receiving API. I'm trying to send data to an API.

Unfortunately i'm facing two issues :

- with my SAP version (SAP BASIS 70207), when using SM59 the HTTP user is limited to 32 characters. there is an oss 2020611 which seems to correct this issue. however, it is not available for my SAP BASIS version.

- when testing SM59 connexion, i always receive http 403 with forbidden status.

it seems to be related to method of connexion. the API i'm using only works with POST method and SM59 only works with GET method.

it seems that with ABAP developpement it is possible to use POST version (example code in following photo) :

however. I'm always receiving http_communication_failure error with message ICM_HTTP_INTERNAL_ERROR when testing an API.

Do you think there is a solution to my problems ?

Do you think we need to apply a support package to create this solution ?

Regards,

Jean-François.

Former Member
0 Kudos

If the web service is RestFul and exposed to internet, why do you need an entry in SM59?

former_member202213
Participant
0 Kudos

Hi Sahil,

that's a good question.

with api, it seems that the version may evolve in the future. hence the url may change as well. it seems easier to change SM59 directly in production system than change the ABAP code and transport TOs.

Former Member
0 Kudos

The idea of a Restful Web Service is it should be Stateless and Loosely coupled and that's why i use it directly.

I will suggest to test your api and the request parameters directly from some other application( like Javascript etc) or using Online HTTP Request sites(like Request Maker, Hurl.it etc) and see if you can connect it. If you are successful, then you should be successful within SAP also.

Thanks
Sahil

A-J-S
Active Participant
0 Kudos
Hi,

I need to consume a webservice which is REST and XML format.

I am aware that SAP has provided standard classes if_rest_client and if_rest_entity in BASIS 731 but our system version is BASIS 702.

Is there any way we can still consume such webservice in 702 version in ABAP ?

Sample input is below and the method is POST

<?xml version="1.0" encoding="UTF-8"?>
<XML_Request>
<MAT>TEST1</MAT>
<OUTPUT_TYPE>3</OUTPUT_TYPE>
</XML_Request>

@sourav.das3
rdiger.plantiko2

Ajith.
0 Kudos
Hi Vedran,

it's a bit late. Anyway for those interested. Neptune software has developed a custom method to handle locks in stateless app. I don't know if it is free to use, in any case it's a good starting point.

In a few words, it starts a batch program that helds the lock between server calls

https://community.neptune-software.com/blogs/lock-handling-mechanism
0 Kudos
Hi,

Anyone can help what is zjobs_tab  and zjob defined?

I am facing unkown errors for that.

Thanks & Regards,

Sweta Gohil
rdiger_plantiko2
Active Contributor
0 Kudos
Hi Sweta,

ZJOBS is only an example, a "database table for an administration of scheduled jobs" (only demo of course, not real). See the definition below. JOBS_TAB is a sorted table with linetype ZJOBS and unique key field ID.

These database tables are not needed if you plan to create an own RESTful service, they are only used as example. I shared the code of the class, so you could get the idea how to design a RESTful service for your own needs.

Kind Regards,

Rüdiger

0 Kudos
Thanks Rudiger for prompt reply.

I am new to this area so trying to replicate your example first and then will go for own scenario.

I am facing another issue.

Type "ZCX_PARSE_ERROR" is unknown. Can you please guide?

Thanks & Regards,

Sweta Gohil.
rdiger_plantiko2
Active Contributor
0 Kudos
Hi Sweta,

the example really was not designed for completely copying it into your system but for just getting the ideas. zcx_parse_error (an exception derived from zcx_error derived from cx_dynamic_check) seems only the tip of the iceberg.

It seems a better strategy to write an own, completely new request handler class for your purpose and then adding the operations for your needs, following the ideas of this blog.

But if you insist: the example class zcl_job_data has the following 44 dependencies in the customer name space. You can look up all the code objects via bsp.mits.ch, by adding object type and object name to the URL. For example, you find the XSLT transformation "zjobs_in" under http://bsp.mits.ch/code/xslt/zjobs_in

But I warn you, this is a tedious operation, you will save much time by starting from the scratch with your own, completely new request handler class.

Kind regards,
Rüdiger

clas zcl_job_repid_variants
clas zcl_json_builder
clas zcx_error
clas zcx_invalid_attribute
clas zcx_not_found
clas zcx_parameter_error
clas zcx_parse_error
doma z_bytes
doma z_packed
doma zpriok
dtel z_bytes
dtel z_packed
dtel zpriok
fugr zutil
fugr zutil_convert
msag ztest
msag zut_all
msag zutil
prog z_ux_console
prog zut_au_forms
prog zutil_perform
prog zz_raise
tabl zindex_range
tabl zjobs
tabl zrange_domvalue
tabl zut_data
tabl zut_hash_element
tabl zut_unix_files
ttyp zindex_range_tab
ttyp zjobs_tab
ttyp zsvartxt_tab
ttyp ztrange_domvalue
ttyp ztyp_abaplist
ttyp ztyp_btcxpm
ttyp zut_array_tab
ttyp zut_hash_tab
ttyp zut_unix_files_tab
type zjson
xslt zabap2json
xslt zjobs
xslt zjobs_in
xslt zjobs2json
xslt zjson2job
xslt zvariants

 
rdiger_plantiko2
Active Contributor
0 Kudos
Ten years later, MIGROS has placed the site bsp.mits.ch to "internal use". This means that the links pointing to the full source code of the example application will not work any more.

But this doesn't matter, since the intent of this blog post was to give an idea of a completely self-designed REST-based web application. No dependency on any framework. Just an ICF service handling the incoming requests and providing answers to the caller.

In the meantime, SAP has provided us with a fully featured REST library, see

https://help.sap.com/doc/saphelp_nw74/7.4.16/en-US/b3/6a1841f79944a899015773f07f424c/frameset.htm

Even more recently, the RESTful application programming model (RAP) incorporates the REST ideas in a bigger architecture, including modelling of the business objects with CDS behaviour definitions, with many elements directly incorporated into the ABAP syntax.

https://help.sap.com/doc/abapdocu_cp_index_htm/CLOUD/en-US/index.htm?file=abenabap_for_rap_bos.htm

However: for simple types of requests or even applications, there is still the option to write a plain and simple ICF request handler as the one layed out in this blog post. The code shows how to read the information from the incoming request (body, header fields, request method, etc.). They can be parsed into ABAP data if necessary. Depending on these data, an action is performed, and finally the response is generated. That's it.