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: 
stefan_schnell
Active Contributor
More and more comes the communication with SAP back end system with standard protocols like HTTP in our focus. So it is easy possible to communicate with an SAP system without any additional libraries. In this post I describe an easy example how to connect an SAP system via HTTP request from PowerShell.

 

Get Data


I start with an easy handle_request method in an ABAP class, called Z_STEFAN, which uses the interface IF_HTTP_EXTENSION. The method detects the request method and delivers always status code 200, which means ok, and a text.
method IF_HTTP_EXTENSION~HANDLE_REQUEST.

DATA:
lv_verb TYPE string
.

lv_verb = SERVER->REQUEST->GET_HEADER_FIELD( name = '~request_method' ).
CHECK lv_verb = 'GET'.

SERVER->RESPONSE->SET_STATUS( code = 200 reason = 'Ok' ).
SERVER->RESPONSE->SET_CDATA( data = 'Hello Stefan' ).

endmethod.

Now it is necessary to define a service with TAC SICF. I define a service, called stefan, at the highest level and I add my class Z_STEFAN at the handler list.



Now it is possible to call this service from outside. Here a PowerShell program to do that. We start with the preparation of the user credentials and define a header for the HTTP request. The requested URI contains the service name, in this case stefan. Then we invoke the web request with the URI, a GET request and the header. If all is okay, we should see the result in the console.
#-Begin-----------------------------------------------------------------

#-Sub Main------------------------------------------------------------
Function Main() {

$User = "BCUSER";
$Password = "minisap";
$Pair = $User + ":" + $Password;
$encodedCreds = [System.Convert]::ToBase64String(`
[System.Text.Encoding]::ASCII.GetBytes($Pair));
$basicAuthValue = "Basic " + $encodedCreds;

$header = New-Object `
"System.Collections.Generic.Dictionary[[String],[String]]";
$header.Add("cache-control", "no-cache");
$header.Add("authorization", $basicAuthValue);
$header.Add("accept", "*/*");
$header.Add("accept-encoding", "gzip, deflate");

$RequstURI = "http://abap:8000/stefan";

Try {
$Result = Invoke-WebRequest -Uri $RequstURI -Method Get `
-Headers $header -ErrorVariable RestError;
} Catch {
} Finally {
If ($Result.StatusCode -eq 200) {
Write-Host $Result.Content;
} Else {
Write-Host $RestError;
}
}

}

#-Main----------------------------------------------------------------
Main;

#-End-------------------------------------------------------------------

And here the result.



 

Set and Get Data


In the next example I modify the class a little bit, so that it is possible to set and get data via an HTTP request. I add a header field called input which contains data in JSON format, in this case the name. With the method deserialize of the class /ui2/cl_json I dismantle the JSON string.
METHOD IF_HTTP_EXTENSION~HANDLE_REQUEST.

TYPES: BEGIN OF ts_record,
key TYPE string,
value TYPE string,
END OF ts_record.

DATA:
lv_verb TYPE string,
lv_input TYPE string,
lt_input TYPE STANDARD TABLE OF ts_record,
ls_input TYPE ts_record,
lv_name TYPE string,
lv_output TYPE string
.

lv_verb = SERVER->REQUEST->GET_HEADER_FIELD( name = '~request_method' ).
CHECK lv_verb = 'GET'.

lv_input = SERVER->REQUEST->GET_HEADER_FIELD( name = 'input' ).

/ui2/cl_json=>deserialize(
EXPORTING
json = lv_input
CHANGING
data = lt_input
).

LOOP AT lt_input INTO ls_input.
CASE ls_input-key.
WHEN 'name'.
lv_name = ls_input-value.
ENDCASE.
ENDLOOP.

lv_output = `Hello ` && lv_name.

SERVER->RESPONSE->SET_STATUS( code = 200 reason = 'Ok' ).
SERVER->RESPONSE->SET_CDATA( data = lv_output ).

ENDMETHOD.

Here the PowerShell script to call this method. I add the new header field input with a JSON string, which is equal to the ts_record structure in the ABAP class.
#-Begin-----------------------------------------------------------------

#-Sub Main------------------------------------------------------------
Function Main() {

$User = "BCUSER";
$Password = "minisap";
$Pair = $User + ":" + $Password;
$encodedCreds = [System.Convert]::ToBase64String(`
[System.Text.Encoding]::ASCII.GetBytes($Pair));
$basicAuthValue = "Basic " + $encodedCreds;

$header = New-Object `
"System.Collections.Generic.Dictionary[[String],[String]]";
$header.Add("cache-control", "no-cache");
$header.Add("authorization", $basicAuthValue);
$header.Add("accept", "*/*");
$header.Add("accept-encoding", "gzip, deflate");

$JSON = '[{"key":"name","value":"Stefan"}]';
$header.Add("input", $JSON);

$RequstURI = "http://abap:8000/stefan?sap-client=001";

Try {
$Result = Invoke-WebRequest -Uri $RequstURI -Method Get `
-Headers $header -ErrorVariable RestError;
} Catch {
} Finally {
If ($Result.StatusCode -eq 200) {
Write-Host $Result.Content;
} Else {
Write-Host $RestError;
}
}

}

#-Main----------------------------------------------------------------
Main;

#-End-------------------------------------------------------------------

The result is exactly the same as in the example before but now you have the possibility to set each name you like.

 

Interface Test


In the next step we will change the design of the class for a better unit testing and using of the functionality in a possible different context. This has no effect on the functionality.
CLASS Z_STEFAN DEFINITION
PUBLIC
CREATE PUBLIC.

PUBLIC SECTION.

INTERFACES IF_HTTP_EXTENSION.

METHODS get_data
IMPORTING
VALUE(iv_input) TYPE string
RETURNING
VALUE(rv_output) TYPE string.

PROTECTED SECTION.

PRIVATE SECTION.

ENDCLASS.



CLASS Z_STEFAN IMPLEMENTATION.


* <SIGNATURE>----------------------------------------------------------+
* | Instance Public Method Z_STEFAN->IF_HTTP_EXTENSION~HANDLE_REQUEST
* +--------------------------------------------------------------------+
* | [--->] SERVER TYPE REF TO IF_HTTP_SERVER
* +---------------------------------------------------------</SIGNATURE>
METHOD IF_HTTP_EXTENSION~HANDLE_REQUEST.

DATA:
lv_verb TYPE string,
lv_output TYPE string
.

lv_verb = SERVER->REQUEST->GET_HEADER_FIELD( name = '~request_method' ).
CHECK lv_verb = 'GET'.

lv_output = me->get_data(
iv_input = SERVER->REQUEST->GET_HEADER_FIELD( name = 'input' )
).

SERVER->RESPONSE->SET_STATUS( code = 200 reason = 'Ok' ).
SERVER->RESPONSE->SET_CDATA( data = lv_output ).

ENDMETHOD.


* <SIGNATURE>----------------------------------------------------------+
* | Instance Public Method Z_STEFAN->GET_DATA
* +--------------------------------------------------------------------+
* | [--->] IV_INPUT TYPE STRING
* | [<-()] RV_OUTPUT TYPE STRING
* +---------------------------------------------------------</SIGNATURE>
METHOD get_data.

TYPES: BEGIN OF ts_record,
key TYPE string,
value TYPE string,
END OF ts_record.

DATA:
lt_input TYPE STANDARD TABLE OF ts_record,
ls_input TYPE ts_record,
lv_name TYPE string
.

/ui2/cl_json=>deserialize(
EXPORTING
json = iv_input
CHANGING
data = lt_input
).

LOOP AT lt_input INTO ls_input.
CASE ls_input-key.
WHEN 'name'.
lv_name = ls_input-value.
ENDCASE.
ENDLOOP.

rv_output = `Hello ` && lv_name.

ENDMETHOD.


ENDCLASS.

And generally is it on this way possible to use e.g. Postman to check the correct functionality of the interface.



 

The German Umlauts Problem


It is not possible to transfer German umlauts via the HTTP request header on this way. But you can pack those kind of information in a HTTP request body. In this case you can't use the GET method for the HTTP request, it is necessary to change it to POST. Here the adjusted method. Now we check the POST method and convert the body data via two function calls in a string.
  METHOD IF_HTTP_EXTENSION~HANDLE_REQUEST.

DATA:
lv_verb TYPE string,
lv_length TYPE i,
lt_bin TYPE STANDARD TABLE OF solisti1,
lv_output TYPE string,
lv_data TYPE xstring,
lv_input TYPE string
.

lv_verb = SERVER->REQUEST->GET_HEADER_FIELD( name = '~request_method' ).
CHECK lv_verb = 'POST'.

CALL FUNCTION 'SCMS_XSTRING_TO_BINARY'
EXPORTING
buffer = SERVER->request->get_data( )
IMPORTING
output_length = lv_length
TABLES
binary_tab = lt_bin.

CALL FUNCTION 'SCMS_BINARY_TO_STRING'
EXPORTING
input_length = lv_length
IMPORTING
text_buffer = lv_input
TABLES
binary_tab = lt_bin.

lv_output = me->get_data(
iv_input = lv_input
).

SERVER->RESPONSE->SET_STATUS( code = 200 reason = 'Ok' ).
SERVER->RESPONSE->SET_CDATA( data = lv_output ).

ENDMETHOD.

Here now the PowerShell script. We add in the header a new field with the content type and the web request invoke contains now a body parameter with the JSON data.
#-Begin-----------------------------------------------------------------

#-Sub Main------------------------------------------------------------
Function Main() {

$User = "BCUSER";
$Password = "minisap";
$Pair = $User + ":" + $Password;
$encodedCreds = [System.Convert]::ToBase64String(`
[System.Text.Encoding]::ASCII.GetBytes($Pair));
$basicAuthValue = "Basic " + $encodedCreds;

$header = New-Object `
"System.Collections.Generic.Dictionary[[String],[String]]";
$header.Add("cache-control", "no-cache");
$header.Add("authorization", $basicAuthValue);
$header.Add("accept", "*/*");
$header.Add("accept-encoding", "gzip, deflate");
$header.Add("content-type" , "application/json; charset=utf-8");

$JSON = '[{"key":"name","value":"Rüdiger"}]';

$RequstURI = "http://abap:8000/stefan?sap-client=001";

Try {
$Result = Invoke-WebRequest -Uri $RequstURI -Method Post `
-Headers $header -Body $JSON -ErrorVariable RestError;
} Catch {
} Finally {
If ($Result.StatusCode -eq 200) {
Write-Host $Result.Content;
} Else {
Write-Host $RestError;
}
}

}

#-Main----------------------------------------------------------------
Main;

#-End-------------------------------------------------------------------





 

Read Any Table and Get Their Content in JSON Format


Here now a tiny modification of the get_data method. Now the name contains a table name and the method delivers the whole content of a table in JSON format.
CLASS Z_STEFAN DEFINITION
PUBLIC
CREATE PUBLIC.

PUBLIC SECTION.

INTERFACES IF_HTTP_EXTENSION.

METHODS get_data
IMPORTING
VALUE(iv_input) TYPE string
RETURNING
VALUE(rv_output) TYPE string.

PROTECTED SECTION.

PRIVATE SECTION.

ENDCLASS.



CLASS Z_STEFAN IMPLEMENTATION.


* <SIGNATURE>----------------------------------------------------------+
* | Instance Public Method Z_STEFAN->GET_DATA
* +--------------------------------------------------------------------+
* | [--->] IV_INPUT TYPE STRING
* | [<-()] RV_OUTPUT TYPE STRING
* +---------------------------------------------------------</SIGNATURE>
METHOD get_data.

TYPES: BEGIN OF ts_record,
key TYPE string,
value TYPE string,
END OF ts_record.

FIELD-SYMBOLS:
<lt_table> TYPE STANDARD TABLE
.

DATA:
lt_input TYPE STANDARD TABLE OF ts_record,
ls_input TYPE ts_record,
lv_name TYPE string,
lo_table TYPE REF TO data
.

/ui2/cl_json=>deserialize(
EXPORTING
json = iv_input
CHANGING
data = lt_input
).

LOOP AT lt_input INTO ls_input.
CASE ls_input-key.
WHEN 'name'.
lv_name = ls_input-value.
ENDCASE.
ENDLOOP.

CREATE DATA lo_table TYPE TABLE OF (lv_name).
ASSIGN lo_table->* TO <lt_table>.

SELECT * INTO TABLE <lt_table> FROM (lv_name).

rv_output = /ui2/cl_json=>serialize( data = <lt_table> ).

ENDMETHOD.


* <SIGNATURE>----------------------------------------------------------+
* | Instance Public Method Z_STEFAN->IF_HTTP_EXTENSION~HANDLE_REQUEST
* +--------------------------------------------------------------------+
* | [--->] SERVER TYPE REF TO IF_HTTP_SERVER
* +---------------------------------------------------------</SIGNATURE>
METHOD IF_HTTP_EXTENSION~HANDLE_REQUEST.

DATA:
lv_verb TYPE string,
lv_length TYPE i,
lt_bin TYPE STANDARD TABLE OF solisti1,
lv_output TYPE string,
lv_data TYPE xstring,
lv_input TYPE string
.

lv_verb = SERVER->REQUEST->GET_HEADER_FIELD( name = '~request_method' ).
CHECK lv_verb = 'POST'.

CALL FUNCTION 'SCMS_XSTRING_TO_BINARY'
EXPORTING
buffer = SERVER->request->get_data( )
IMPORTING
output_length = lv_length
TABLES
binary_tab = lt_bin.

CALL FUNCTION 'SCMS_BINARY_TO_STRING'
EXPORTING
input_length = lv_length
IMPORTING
text_buffer = lv_input
TABLES
binary_tab = lt_bin.

lv_output = me->get_data(
iv_input = lv_input
).

SERVER->RESPONSE->SET_STATUS( code = 200 reason = 'Ok' ).
SERVER->RESPONSE->SET_CDATA( data = lv_output ).

ENDMETHOD.


ENDCLASS.



 

Could it be easier possible to communicate with an SAP system?