Skip to Content
Technical Articles
Author's profile photo Andre Fischer

How to execute actions or function imports in remote OData services from Steampunk

The problem – No support for functions and actions by the remote OData proxy client

A customer asked me how to use the remote OData proxy client in order to call a function import in their on premise SAP ERP from whithin their SAP BTP ABAP Environment.

This is unfortunately not possible using the remote OData proxy client since in Steampunk you can only create a remote OData proxy client based on a service conumption model which currently does not yet support function imports or actions.

As a result, code like the following will NOT work in Steampunk:

data:  
client_proxy TYPE REF TO /iwbep/if_cp_client_proxy,  
function_resource type ref to /iwbep/if_cp_resource_function.  
function_resource = client_proxy->create_resource_for_function( 'PROMOTE_EMPLOYEE')  

With an upcoming update we plan to add a support for function imports and actions.

The solution – Use a http client

I thought about a workaround to overcome this technical restriction and came up with some ABAP sample code that shows how to use a http client based on the interface

In the following I am showing a sample that performs a POST request using the following URL:

https://sapes5.sapdevcenter.com/sap/opu/odata/IWBEP/GWSAMPLE_BASIC/SalesOrder_Confirm?sap-client='002'&SalesOrderID='0500010020'

Coding explained

The class contains a method execute_action( ) that takes the name of the action and the parameter(s) as a list of name / value pairs.

This method first retrieves the CSRF token which has to be sent in a http header when executing the POST request.

 

 

ABAP Code:

CLASS zact_test_call_action DEFINITION
  PUBLIC
  FINAL
  CREATE PUBLIC .

  PUBLIC SECTION.

    INTERFACES if_oo_adt_classrun .
  PROTECTED SECTION.
  PRIVATE SECTION.
    METHODS execute_action
      IMPORTING i_action_name      TYPE string
                i_query_parameters TYPE if_web_http_request=>name_value_pairs OPTIONAL
      RETURNING VALUE(response)    TYPE string
      RAISING   cx_http_dest_provider_error
                cx_web_http_client_error .
ENDCLASS.



CLASS zact_test_call_action IMPLEMENTATION.


  METHOD if_oo_adt_classrun~main.

    DATA query_parameters TYPE if_web_http_request=>name_value_pairs  .
    DATA action_name TYPE string.

    query_parameters = VALUE #( ( name = 'SalesOrderID' value = '0500010020' ) ).
    action_name = 'SalesOrder_Confirm'.

    TRY.
        DATA(response) = execute_action(
          EXPORTING
            i_action_name        = action_name
            i_query_parameters   = query_parameters
        ).
        out->write( |response: { response } | ).
      CATCH cx_http_dest_provider_error INTO DATA(http_dest_provider_error).
        "handle exception
        out->write( http_dest_provider_error->get_longtext(  ) ).
      CATCH cx_web_http_client_error INTO DATA(web_http_client_error).
        "handle exception
        out->write( web_http_client_error->get_longtext(  ) ).
    ENDTRY.

  ENDMETHOD.

  METHOD execute_action.

    DATA(demo_mode) = abap_false.

    DATA http_header_fields  TYPE if_web_http_request=>name_value_pairs  .

    http_header_fields = VALUE #( ( name = if_web_http_header=>accept value = |application/json| )
                                  ( name = if_web_http_header=>content_type value =  |application/json|  )
                                  ( name = 'x-csrf-token' value = 'fetch' ) ).

    DATA(service_relative_url) = '/sap/opu/odata/IWBEP/GWSAMPLE_BASIC/'.
    DATA(sap_client) = '002'.
    DATA(destination_name_in_dest_srv) = 'ES5'.

    "Destination Required for Basic Authentication. Credentials are stored in the SAP BTP destination service
    DATA(lo_http_destination) = cl_http_destination_provider=>create_by_cloud_destination(
                                  i_name                  =  'ES5'
                                  i_authn_mode            = if_a4c_cp_service=>service_specific
                                ).
    DATA(relative_url) = |{ service_relative_url }?sap-client={ sap_client }|.

    DATA(http_client) = cl_web_http_client_manager=>create_by_http_destination( lo_http_destination ).

    "remove any white spaces since the method set_uri_path does not check for white spaces
    CONDENSE relative_url NO-GAPS.

    " GET CSRF token
    http_client->get_http_request( )->set_uri_path( i_uri_path = relative_url ).
    http_client->get_http_request( )->set_header_fields( http_header_fields ).

    DATA(http_response) = http_client->execute( if_web_http_client=>get ).  "--> works
    DATA(http_status_code) = http_response->get_status( ).
    DATA(x_csrf_token) =  http_response->get_header_field( 'x-csrf-token' ).
    DATA(http_response_body) =  http_response->get_text( ).


    " Prepare POST request
    IF demo_mode = abap_false.
      relative_url = |{ service_relative_url }{ i_action_name }?sap-client={ sap_client }&{ i_query_parameters[ 1 ]-name }= '{ i_query_parameters[ 1 ]-value }' |.
      "remove any white spaces since the method set_uri_path does not check for white spaces
      CONDENSE relative_url NO-GAPS.
    ELSE.
      EXIT.
    ENDIF.

    http_client->get_http_request( )->set_uri_path( i_uri_path = relative_url ).

    http_header_fields = VALUE #( ( name = if_web_http_header=>accept value = |application/json| )
                                  ( name = 'x-csrf-token' value =  x_csrf_token ) ).

    http_client->get_http_request( )->set_header_fields( http_header_fields ).

*   DATA(http_request_body) = | { add json string } |.
*   http_client->get_http_request( )->set_text( http_request_body ).

    http_response = http_client->execute( if_web_http_client=>post ).
    http_status_code = http_response->get_status( ).
    response =  http_response->get_text( ).

  ENDMETHOD.

ENDCLASS.

 

 

Assigned Tags

      2 Comments
      You must be Logged on to comment or reply to a post.
      Author's profile photo Aocheng Yang
      Aocheng Yang

      Great blog Andre,

      I'm encoutring a problem with this approach. When "if_web_http_client=>post" is executed, I get a return that says "Access denied for MyVirtualHostName:XXX(port used for HTTP)". It looks like it's calling the URL with HTTP port, instead of HTTPS port. My coud connector is using HTTPS and so is the port so it should call the URL with HTTPS port. Is it my destination setup that is the problem? Were you able to POST succesfully with HTTPS connection?

      My setup is as follows:

      • Cloud connector connects on-premise and BTP with HTTPS protocol and virtual host is used.
      • A new destination is created on BTP with the connection with, Protocol= HTTP, URL = virtual host in cloud connector, authentication=basic auth . Connection test succesful.
      • I'm using your code in this blog and my destination is specified in cl_http_destination_provider. Get call is succesful but POST call fails with above error.
      Author's profile photo Bjoern S.
      Bjoern S.

      Hello Aocheng,

      we had the same problem with the cloud connector and opened the route, but mapped the port 80 to 443 for internal traffic. But port 80 means also not, that the traffic is insecure. After opening the port 80 in CC, the example should work without problems.

       

      Greetings

      Björn