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.
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.
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.
tipSummary: 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 usingPOST
. Nevertheless, if you want to have your interfaces RESTful, consider usingPUT
andPOST
correctly. -
GET
Getting a Resource at a given path, for exampleGET /questions
orGET /questions/{id}
-
DELETE
Deleting a Resource at a given path, for exampleDELETE /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!
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 methodexecute
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 fieldid
. 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 methodexecute
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 methodexecute
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 be200
and returning the type specified. If not given, the response status code for success will be204
. -
execute
This method is responsible for handling a single request, meaning the actual interface handling. Input: Request Attributes, Request Body as typeany
. Just cast to the concrete type specified in its dedicated methods. Output: Response Body, if specified inget_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
and204
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 classTX
).
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 {}
.
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
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 nodetemplate
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:/UBC/CHECK_AUTH
(default not set) Set toX
, 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 toX
, 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.