This article will help you to set up and execute HTTPS calls to API endpoints, in this case Collibra, but it can work for other connections too.
The request was to setup a connection to Collibra, and to call an endpoint with a file attachment to create assets, then calling another endpoint with a parameter to finalize the result.
Steps:
- Setup the connection
- Create the client object
- Calling an endpoint
- Send the command and retrieve the response
- Calling an endpoint with parameters
- Adding a file as attachment with parameters
- Storing the result
- Setup the connection
We used SM59 to setup a connection to Collibra, this has the advantage that usernames/passwords need not to be stored in the code or in parameters. - Create the client object
We used a global for this:
CLASS-DATA obj_http_client TYPE REF TO if_http_client.
CONSTANTS co_destination TYPE c LENGTH 60 VALUE 'COLLIBRA CONNECTION'.
And used a separate class method to create the connection:
CALL METHOD cl_http_client=>create_by_destination
EXPORTING
destination = co_destination
IMPORTING
client = obj_http_client
EXCEPTIONS
argument_not_found = 1
plugin_not_active = 2
internal_error = 3
OTHERS = 4.
“ Set the properties, watch out for the protocol version, also used in SM59 setup
obj_http_client->propertytype_accept_cookie = if_http_client=>co_enabled.
obj_http_client->request->set_version( if_http_request=>co_protocol_version_1_1 ).
obj_http_client->propertytype_logon_popup = if_http_client=>co_disabled.3. Calling an endpoint
We used separate methods for each endpoint, because the call usually differs.
We are also using a prefix here, which is globally declared.
CONSTANTS co_prefix TYPE text60 VALUE '/rest/2.0'.And in the method we use:
“Construct uri
lv_uri = co_prefix && '/import/results/' && [Insert Job ID here] && '/summary'.
“Attach to client
cl_http_utility=>set_request_uri( request = obj_http_client->request uri = lv_uri ).
“See endpoint documentation regarding GET/POST etc
obj_http_client->request->set_method('GET').
4. Now we send out the command and capture the result, of course this is also put in a separate method:
CALL METHOD obj_http_client->send
EXPORTING
timeout = 1200
EXCEPTIONS
http_communication_failure = 1
http_invalid_state = 2
http_processing_failed = 3.
“Communication errors can be called this way:
IF sy-subrc <> 0.
CALL METHOD obj_http_client->get_last_error(
IMPORTING
code = gv_subrc
message = gv_errortext ).
WRITE: / 'communication_error( send )',
/ 'code: ', gv_subrc, 'message: ', gv_errortext.
gv_error = abap_true.
EXIT.
ENDIF.
* Receive Result, the get_cdata will get the actual response!
CALL METHOD obj_http_client->receive
EXCEPTIONS
http_communication_failure = 1
http_invalid_state = 2
http_processing_failed = 3.
IF sy-subrc <> 0.
CALL METHOD obj_http_client->get_last_error(
IMPORTING
code = gv_subrc
message = gv_errortext ).
WRITE: / 'communication_error( receive )',
/ 'code: ', gv_subrc, 'message: ', gv_errortext.
gv_error = abap_true.
EXIT.
ENDIF.
“ Response header fields can be extracted using
“ obj_http_client->response->get_header_fields( CHANGING fields = lt_fields ).
" Response body is a JSON structure....
lv_resp = obj_http_client->response->get_cdata( ).
5. Calling an endpoint with parameters.
Look at the ’Content_type’ that the endpoint expects!
In case of ‘application/json’ it is easy:
obj_http_client->request->set_header_field( name = 'Content-Type' value = `application/json` ).
obj_http_client->request->set_cdata( data = `{ "tagNames": [ "Hello" ] }` ).
In case of multipart/form data it gets more tricky. The set_cdata still can be used, but the body of the request needs to be constructed. The key to success was to press the “Code” button in Postman. This gave the solution:
Content-Type: multipart/form-data; boundary=----WebKitFormBoundary7MA4YWxkTrZu0gW
------WebKitFormBoundary7MA4YWxkTrZu0gW
Content-Disposition: form-data; name="finalizationStrategy"
REMOVE_RESOURCES
------WebKitFormBoundary7MA4YWxkTrZu0gW--
The first part is the request header, followed by the request body.
In code, it looks like this:
cl_http_utility=>set_request_uri( request = obj_http_client->request uri = lv_uri ).
obj_http_client->request->set_method('POST').
obj_http_client->request->set_header_field( name = 'Content-Type' value = `multipart/form-data; boundary=----WebKitFormROBndary7MA4YWxkTrZu0gW` ).
lv_request_body = `Content-Type: multipart/form-data; boundary=----WebKitFormROBndary7MA4YWxkTrZu0gW` && cl_abap_char_utilities=>cr_lf &&
cl_abap_char_utilities=>cr_lf &&
`------WebKitFormROBndary7MA4YWxkTrZu0gW` && cl_abap_char_utilities=>cr_lf &&
`Content-Disposition: form-data; name="finalizationStrategy"` && cl_abap_char_utilities=>cr_lf &&
cl_abap_char_utilities=>cr_lf &&
`REMOVE_RESOURCES` && cl_abap_char_utilities=>cr_lf &&
`------WebKitFormROBndary7MA4YWxkTrZu0gW--`.
* " Set the request body
obj_http_client->request->set_cdata( data = lv_request_body ).
“ Now send, get response etc
The boundary string needs to be a unique string, Postman will regenerate this with each call, I am using the same string over and over.
6. Adding a file as attachment with parameters.
What we need to do here is to add a multipart. Body parameters are set in the header of the multipart:
DATA: lo_part TYPE REF TO if_http_entity.
DATA: lv_raw TYPE xstring.
obj_http_client->request->if_http_entity~set_content_type( content_type = 'multipart/form-data' ).
obj_http_client->request->set_method('POST').
lo_part = obj_http_client->request->if_http_entity~add_multipart( ).
“ Set body parameters, actually stored via de header...
lo_part->set_header_field( name = 'Content-Disposition' value = 'form-data; name="file"; filename="assets.json"' ).
OPEN DATASET p_file FOR INPUT IN LEGACY BINARY MODE CODE PAGE '1100'.
IF sy-subrc = 0.
READ DATASET p_file INTO lv_raw.
IF sy-subrc = 0.
CALL METHOD lo_part->append_data
EXPORTING
data = lv_raw.
ENDIF.
ENDIF.
CLOSE DATASET p_file.
lo_part->set_data( data = lv_raw ).
lo_part->set_formfield_encoding( formfield_encoding = obj_http_client->request->if_http_entity~co_formfield_encoding_raw ).
“ Now send, get response etc.
7. Storing the result:
I have created a separate table (BW ADSO in this case) to store the data, using the current time stamp and a row number.
The JSON output is to be stored in name/value fields, to be able to do this, the JSON was converted using this code, which will return a simple table that can be LOOP’ed over
DATA: lv_xstring TYPE xstring,
lt_return TYPE STANDARD TABLE OF bapiret2,
ls_return TYPE bapiret2,
lv_xml TYPE string.
DATA(reader) = cl_sxml_string_reader=>create(
cl_abap_codepage=>convert_to( i_json ) ).
DATA(writer) = cl_sxml_string_writer=>create(
type = if_sxml=>co_xt_xml10 ).
reader->next_node( ).
reader->skip_node( writer ).
lv_xml = cl_abap_codepage=>convert_from( CAST cl_sxml_string_writer( writer )->get_output( ) ).
lv_xstring = cl_abap_codepage=>convert_to( source = lv_xml codepage = `UTF-8` ).
CALL FUNCTION 'SMUM_XML_PARSE'
EXPORTING
xml_input = lv_xstring
TABLES
xml_table = e_xml_table
return = lt_return
EXCEPTIONS
OTHERS = 0.
That's all, hope you found it interesting/helpful.