Skip to main content

RESTful webservices (UBC.rest)

Intro

UBC - Unified Backend Connector comes with a powerful component for designing, documenting and testing of RESTful webservices. All integrated into your well-known ABAP-environment, there is no need for knowing any specific REST-details! In contrast to OData you are completely free in the interface design - do not let the technology limiting you!

Open API specification / Swagger UI

In UBC, every interface is well described by OpenAPI and can be displayed and tested using Swagger UI. See also official Open API specification. Currently the standard message format for every interface is JSON, as it is the one with the lowest payload size and a high market acceptance (state-of-the-art).

In UBC, Swagger UI is used for testing and displaying all the implemented interfaces.

warning

Internet Explorer is not supported by Swagger UI. Make sure to open the link to Swagger UI in a modern browser.

Swagger UI is available in your system by:

  • calling transaction /UBC/SWAGGER
  • directly open the URL <<sap_base_url>>/sap/bc/bsp/ubc/swagger/default.htm in your browser Make sure to replace <<sap_base_url>> with url/hostname pointing to your SAP system.

SAP Swagger UI Just choose your desired Interface you previously developed or want to check and click on "Try it out". Fill in the request body/attributes and "Execute". You can have a look at the request in your browser's network tab or in Swagger directly. Your Interface implementation will then just be triggered synchronously. If there is the need for any asynchronous interfaces, consider using UBC.io component.

Basically UBC comes with a few pre-delivered interfaces, which are described below. The pre-delivered interfaces are all prefixed with /ubc/ and organized in a /ubc/ operations group. All your custom developed interfaces are grouped in a second operations group called custom. It is therefore recommended to prefix your interfaces logically. You are completely free to use any kind of prefix, except /ubc/.

Custom Interfaces

All details regarding API specification, conversion, etc. are handled within the REST framework itself, simply focus on what matters for the use case: the interface design and its business logic.

Development

Create a new class representing your REST interfaces holding together all the possible operations for a single resource:

  • Modifying Requests Basically there are 2 possible operations for modifying a resource:

    • POST is for simply changing or creating a resource where the key is not (yet) known or just indirectly wrapped within the payload, for example: POST /questions POST is NOT idempotent. So if we retry the request N times, we will end up having N resources.
    • PUT is for creating/updating a resource where the full resource key is known by the caller. For example: PUT /questions/{id} PUT method is idempotent. So if we retry a request multiple times, that should be equivalent to a single request invocation.
    tip

    Summary: Technically you can always use POST for simplicity reasons, if you ensure at sender side that requests are just sent once. In a M2M environment this is usually ensured, so we just recommend using POST. Nevertheless, if you want to have your interfaces RESTful, consider using PUT and POST correctly.

  • GET Getting a Resource at a given path, for example GET /questions or GET /questions/{id}

  • DELETE Deleting a Resource at a given path, for example DELETE /questions/{id}

Letting your new class inherit from /UBC/CL_REST_RESOURCE gives the possibility to redefine the abstract methods:

  • get_method_post - returning an object inheriting from class /UBC/CL_REST_METHOD_POST
  • get_method_put - returning an object inheriting from class /UBC/CL_REST_METHOD_PUT
  • get_method_get - returning an object inheriting from class /UBC/CL_REST_METHOD_GET
  • get_method_delete - returning an object inheriting from class /UBC/CL_REST_METHOD_DELETE

Implementing such a method and returning an object means that the operation is basically supported!

info

Only implement the methods for the operations you would like to support

You can implement the classes as local classes within your resource global class (or define new global ones, its up to you). Just a few words about the abstract methods of the dedicated class for the operations:

  • get_type_request_attributes If your resource accepts Request attributes, return a reference to an ABAP structure containing the possible fields. In method execute you can then just cast the dedicated importing parameter (type any) to this concrete type, representing the request attributes. For example, if your Resource is located at /questions/{id} you need to return an ABAP structure, containing the field id. This is just optional and will be interpreted by Open API/Swagger, wether and what returned from this method.

  • get_type_request_body If your resource accepts a Request body, e.g. containing the payload of a message, return a reference to an ABAP type describing the content. In method execute you can then just cast the dedicated importing parameter (type any) to this concrete type, representing the request body. This is just optional and will be interpreted by Open API/Swagger, wether and what returned from this method.

  • get_type_response_body If your resource should return a Response body, e.g. containing results of posting an object, return a reference to an ABAP type describing the content (e.g. BAPIRET2_T). In method execute you can then just cast the dedicated exporting parameter (type any) to this concrete type, representing the response body. This is just optional and will be interpreted by Open API/Swagger. If given, the response status code for success will be 200 and returning the type specified. If not given, the response status code for success will be 204.

  • execute This method is responsible for handling a single request, meaning the actual interface handling. Input: Request Attributes, Request Body as type any. Just cast to the concrete type specified in its dedicated methods. Output: Response Body, if specified in get_type_response_body. Just cast to the concrete type and return the data. From an integration perspective: If the execution has to fail (due to an error, e.g. the requested object is locked) do not return just some messages in response body, rather raise an exception of type /UBC/CX_REST. This makes the request fail and not showing success on the sender side requiring parsing of messages. You can do that in addition to the exception, but rather no error control by responses. Always ensure having your own transaction handling (COMMIT WORK).

  • configure_documentation This method will be called when rendering Open API specification, in order to get the appropriate documentation for:

    • the operation itslef (description and title)
    • single fields in attributes/body. If nothing given, the descriptions will be derived from the DDIC (Data element, Structures, ...)
    • Response status description - if the default for 200 and 204 is not enough

    Best practice is to document the operation (title and description) to have an high level overview which interface is doing what. Writing just few sentences about the interface makes your life easier afterwards.
    You are free to use harcoded text/text symbols or translatable docu objects (SE61 with class TX).

tip

As a reference and quick-start you can copy from the template class /UBC/CL_REST_RESOURCE_TEMPLATE and just remove the unncesseary things. For a better overview, please find a hardcopy below.

Global Class /UBC/CL_REST_RESOURCE_TEMPLATE:

"! <p class="shorttext synchronized" lang="en">Example REST Resource supporting all operations</p>
"! This class just implements all possible operations
"! <br>You are free to copy the template class as a quick-start and just remove unnecessary things
class /ubc/cl_rest_resource_template definition
public
inheriting from /ubc/cl_rest_resource
create public.

public section.
protected section.
methods get_method_delete redefinition.
methods get_method_get redefinition.
methods get_method_post redefinition.
methods get_method_put redefinition.
private section.
endclass.

class /ubc/cl_rest_resource_template implementation.

method get_method_delete.
"enabling DELETE operation for resource
ro_result = new lcl_method_delete( me ).
endmethod.

method get_method_get.
"enabling GET operation for resource
ro_result = new lcl_method_get( me ).
endmethod.

method get_method_post.
"enabling DELETE operation for resource
ro_result = new lcl_method_post( me ).
endmethod.

method get_method_put.
"enabling PUT operation for resource
ro_result = new lcl_method_put( me ).
endmethod.
endclass.

Local Types Section of /UBC/CL_REST_RESOURCE_TEMPLATE:

"! GET Operation example
class lcl_method_get definition create public inheriting from /ubc/cl_rest_method_get.
protected section.
types:
begin of s_request_attributes,
id type matnr,
end of s_request_attributes.
types:
begin of s_response_body,
id type matnr,
description type maktx,
end of s_response_body.
methods configure_documentation redefinition.
methods get_type_request_attributes redefinition.
methods get_type_response_body redefinition.
methods execute redefinition.
endclass.

class lcl_method_get implementation.
method configure_documentation.
io_documentation->configure_method(
exporting
iv_summary = 'Getting Resource data (Title)'
iv_description_docu_object = 'ZUBC_REST_RES_GET_TEMPL' "create in SE61 -> class TX, name ZUBC_REST_RES_GET_TEMPL
).
endmethod.

method get_type_request_attributes.
rr_data = new s_request_attributes( ).
endmethod.

method get_type_response_body.
rr_data = new s_response_body( ).
endmethod.

method execute.
"cast type any to concrete types, specified by above methods
data(lr_request_attributes) = ref s_request_attributes( iv_request_parameters ).
data(lr_response_body) = ref s_response_body( ev_response_body ).

"typed access to input
if lr_request_attributes->id = '123'.
"let the request fail
raise exception type /ubc/cx_rest
message id 'V1' number '899' with 'Failed to access data' ##NO_TEXT.
endif.

"access the response body also directly
lr_response_body->description = 'test' ##NO_TEXT.
endmethod.
endclass.

"! POST Operation example
class lcl_method_post definition create public inheriting from /ubc/cl_rest_method_post.
protected section.
types:
begin of s_request_object,
id type matnr,
description type maktx,
end of s_request_object.
methods configure_documentation redefinition.
methods get_type_request_attributes redefinition.
methods get_type_request_body redefinition.
methods get_type_response_body redefinition.
methods execute redefinition.
endclass.

class lcl_method_post implementation.
method configure_documentation.
io_documentation->configure_method(
exporting
iv_summary = 'Posting Resource data (Title)'
iv_description_docu_object = 'ZUBC_REST_RES_POST_TEMPL' "create in SE61 -> class TX, name ZUBC_REST_RES_POST_TEMPL
).
endmethod.

method get_type_request_attributes.
return. "no request attributes in this case, but technically possible
endmethod.

method get_type_request_body.
rr_data = new s_request_object( ).
endmethod.

method get_type_response_body.
rr_data = new s_request_object( ).
endmethod.

method execute.
"cast type any to concrete types, specified by above methods
data(lr_request_attributes) = ref s_request_object( iv_request_body ).
data(lr_response_body) = ref s_request_object( ev_response_body ).

"typed access to input
if lr_request_attributes->id = '123'.
"let the request fail
raise exception type /ubc/cx_rest
message id 'V1' number '899' with 'Failed to access data' ##NO_TEXT.
endif.

"access the response body also directly
lr_response_body->description = 'test' ##NO_TEXT.

"always ensure having your own transaction handling
commit work.
endmethod.
endclass.

"! PUT Operation example
class lcl_method_put definition create public inheriting from /ubc/cl_rest_method_put.
protected section.
types:
begin of s_request_attributes,
id type matnr,
end of s_request_attributes,
begin of s_request_body,
description type maktx,
end of s_request_body,
begin of s_response_body,
id type matnr,
description type maktx,
end of s_response_body.
methods get_type_request_attributes redefinition.
methods get_type_request_body redefinition.
methods get_type_response_body redefinition.
methods execute redefinition.
methods configure_documentation redefinition.
endclass.

class lcl_method_put implementation.
method configure_documentation.
io_documentation->configure_method(
exporting
iv_summary = 'Putting Resource data (Title)'
iv_description_docu_object = 'ZUBC_REST_RES_PUT_TEMPL' "create in SE61 -> class TX, name ZUBC_REST_RES_PUT_TEMPL
).
endmethod.

method get_type_request_attributes.
rr_data = new s_request_attributes( ).
endmethod.

method get_type_request_body.
rr_data = new s_request_body( ).
endmethod.

method get_type_response_body.
rr_data = new s_response_body( ).
endmethod.

method execute.
"cast type any to concrete types, specified by above methods
data(lr_request_attributes) = ref s_request_attributes( iv_request_attributes ).
data(lr_request_body) = ref s_request_body( iv_request_body ).
data(lr_response_body) = ref s_response_body( ev_response_body ).

"typed access to input
if lr_request_attributes->id = '123'.
"let the request fail
raise exception type /ubc/cx_rest
message id 'V1' number '899' with 'Failed to access data' ##NO_TEXT.
endif.

"access the response body also directly
lr_response_body->id = lr_request_attributes->id.
lr_response_body->description = lr_request_body->description.

"always ensure having your own transaction handling
commit work.
endmethod.
endclass.

"! DELETE Operation example
class lcl_method_delete definition create public inheriting from /ubc/cl_rest_method_delete.
protected section.
types:
begin of s_request_attributes,
id type matnr,
end of s_request_attributes.
methods configure_documentation redefinition.
methods get_type_request_attributes redefinition.
methods get_type_response_body redefinition.
methods execute redefinition.
endclass.

class lcl_method_delete implementation.
method configure_documentation.
io_documentation->configure_method(
exporting
iv_summary = 'Deleting a Resource (Title)'
iv_description_docu_object = 'ZUBC_REST_RES_DEL_TEMPL' "create in SE61 -> class TX, name ZUBC_REST_RES_DEL_TEMPL
).
endmethod.

method get_type_request_attributes.
rr_data = new s_request_attributes( ).
endmethod.

method get_type_response_body.
"return bapiret messages in body
rr_data = new bapiret2_t( ).
endmethod.

method execute.
"cast type any to concrete types, specified by above methods
data(lr_request_attributes) = ref s_request_attributes( iv_request_attributes ).
data(lr_response_body) = ref bapiret2_t( ev_response_body ).

"typed access to input
if lr_request_attributes->id = '123'.
"let the request fail
raise exception type /ubc/cx_rest
message id 'V1' number '899' with 'Failed to access data' ##NO_TEXT.
endif.

"access the response body also directly
insert value #( id = 'V1' number = '899' type = 'S' message_v1 = 'Deleted.' )
into table lr_response_body->* ##NO_TEXT.

"always ensure having your own transaction handling
commit work.
endmethod.
endclass.

Handling files

Handling of files is done by just sending the base64 encoded byte data in the payload of your request. In ABAP you only need to specify a field within your payload with type xstring. We then expect the sender to send base64 encoded data in this field. Example:

types begin of s_request_body.
types file type xstring.
types begin of s_request_body.

have to be sent like:

{
"file": "base64 encoded byte data"
}

Please consider using gzip to further compress the file data and save bandwith. From an ABAP perspective: Just change your type of the field in request body to type /ubc/xstring_base64_gzip. From a sender perspective: First compress your file data using gzip and then encode to base64.

Handling anonymous objects

Anonymous objects/attributes, where its definition is not known in ABAP yet (can be compared to "type any") have to use the data element /UBC/JSON. The sender then can just send "any object" and it won't be converted to an ABAP type. The object will be represented basically as a string in your resource implementation. Its your responsibility to parse the anonymous object, e.g. depending on some metadata. Example:

types begin of s_request_body.
types type type string.
types anonymous_object type /ubc/json.
types begin of s_request_body.

have to be sent like:

{
"type": "anonymous_1",
"anonymous_object": {
"field_1": "value_1",
"field_2": "value_2"
}
}

Customizing

You can find the customizing all the REST-Interfaces in: SAP Reference IMG -> Cross Application Components -> Unified Backend Connector (UBC) -> REST Configuration or by just calling transaction /UBC/CUSTOMIZING and going to REST configuration. Simply choose a path for your new REST resource and link to your previously created implementation. Always start with a /. Consider grouping your resources logically! For example /templates/example1 can point to a new ABAP class /ubc/cl_rest_resource_template. For every path variation, define an own entry. You can link more entries to the same implementation. So if your example has also a GET operation with "request attributes", also register a path for /templates/example1/{id} pointing to /ubc/cl_rest_resource_template. The path has to be valid according to the JAX-RS specification. Basically divide by / and wrapping "request attributes" by {}.

info

Summary: Defining a new resource in customizing at /templates/example1/{id} makes it accessible at <<sap_base_url>>/ubc/templates/example1/{id}. Please consider the implicit /ubc prefix and that your request attributes in path are matching with your type in get_type_request_attributes implementation.

Standard Interfaces

UBC comes with a pre-delivered set of standard operations. Those are not mentioned in detail, see the dedicated documentation like BO instead. Detailed documentation, fields, field lengths and general schemas etc. can be directly accessed in Swagger UI.

Authentication /ubc/auth

info

This section only applies if Parameter /UBC/CSRF_CHECK was set for SICF node. (see Configuration/Security for details)

Before any changing operation (POST, PUT, DELETE) a X-CSRF token has to be requested. This token can technically be retreived from each GET request, but for simplicity reasons there is a specific authentication resource /ubc/auth which does exactly nothing but returning this token if requested. Be sure to apply cookies and X-CSRF token from response to each subsequent change operation received from this response.

Available methods: GET Request-Headers: X-CSRF-Token=Fetch Response:

  • x-csrf-token in response headers
  • get all cookies specified in header set-cookie

Configuration/Security

There only is one SICF node visible in the standard delivery: /ubc. That's also why all the REST services starts with the prefix /ubc. Basically the authentication details can be specified there as per standard. In a default setting, a caller has to specify BASIC authentication details. There is no need for any additional authorization/configuration needed. You can test your Requests in Swagger UI and/or your preferred tool like Postman. The usage of dedicated REST services can be further restricted:

  • It is possible to create additional "sub" SICF nodes below /ubc. It has to match to your chosen REST path. So for example: You can create a new SICF node template below /ubc to handle all requests going to /ubc/template/* This can be useful if you want to configure different authorization details or other SICF configuration things. Please just consider to specify the same handler class as for /ubc.
  • Parameters for a SICF node in GUI configuration, just edit /ubc node or its childs: Parameter for SICF node
    • /UBC/CHECK_AUTH (default not set) Set to X, if you want to further secure every REST interface call by checking authorization for authorization object /UBC/REST. (only checking the handler path in customizing)
    • /UBC/CHECK_CSRF (default not set) Set to X, if a CSRF check has to be made for every changing operation. A caller have to request a token with a previous GET call. In a M2M communication, where this product is mostly used, this does not makes too much sense. Therefore it is not activated by default. You can finde more details about Cross-site request forgery here.