‎2016 Feb 10 8:56 PM
We have a need to connect to an external HTTPS REST service from ABAP that uses JSON.
We are using straight ABAP via SAPGui, not Eclipse, and we are not on 7.4 yet so I can't use the new standard SAP JSON class.
I downloaded and installed the ZJSON stuff, but I'm having trouble finding clear examples describing how it should be used.
So far, I am calling the CL_HTTP_CLIENT class CREATE_BY_DESTINATION method to open a connection to the external service using Basic Authentication. They have given us a base URL with various method subpaths depending upon the specific function we need to call.
My trouble spot has been trying to call their API methods where I need to pass parameters but I'm having trouble with the HTTP / REST / JSON syntax using the ABAP CL_HTTP_CLIENT class.
I am calling CL_HTTP_CLIENT->REQUEST->SET_HEADER_FIELD to set the header values:
'~server_protocol' = 'HTTP/1.1'
'Accept' = 'application/json'
'Content-Type' = 'application/json; charset=utf-8'
I am calling CL_HTTP_CLIENT->REQUEST->SET_METHOD to set the request method to "POST"
I am also calling CL_HTTP_UTILITY=>SET_REQUEST_URI to set the full method path (after the base URL), including the specific method we need to use.
I have tried setting the required method parameters as form fields, but I think they need it to be in JSON format.
As a test, I hardcoded what they are expecting and called CL_HTTP_CLIENT->REQUEST->SET_CDATA but their server returns a very length HTML page instead of the expected return parameters wrapping in JSON syntax.
Am I on the right track?? Does the CL_HTTP_CLIENT->REQUEST->SET_CDATA (or SET_DATA) method place the specified content into the HTTP REST "payload" or should I be doing something else?
Thank you so much for any assistance you can provide.
Bryan
‎2016 Feb 10 10:26 PM
Bryan,
In my experience, if you receive a lengthy HTML string as a response when attempting to call a REST service that string usually represents some sort of HTML error page. It might be helpful to download that complete string and examine it in a notepad-like tool (or even save it to a .htm file and open it with your browser) to see if that offers more clues as to what went wrong.
If that doesn't prove to help diagnose the issue, I can offer a basic template for using the CL_HTTP_CLIENT class that is inspired by some code I wrote to link up my SAP instance with a third-party tool. There are some differences compared to your approach but hopefully this'll get help get you to finding the response you are looking for:
* Using the this CREATE_BY_URL can simplify certain aspects of using this class
DATA lo_http_client TYPE REF TO if_http_client.
cl_http_client=>create_by_url(
EXPORTING
url = 'https://rest.test.com/api/docs/doc_id'
IMPORTING
client = lo_http_client
EXCEPTIONS
argument_not_found = 1
plugin_not_active = 2
internal_error = 3
OTHERS = 4 ).
IF sy-subrc <> 0.
RETURN.
ENDIF.
* Remember to authenticate
lo_http_client->authenticate(
username = 'my-username'
password = 'my-password'
).
* Hardcode your payload here for testing purposes.
DATA lv_payload TYPE string.
lv_payload = '{"add":{"name":"my-payload"}}'.
* Convert that payload to xstring.
DATA lv_payload_x TYPE xstring.
CALL FUNCTION 'SCMS_STRING_TO_XSTRING'
EXPORTING
text = uv_payload
IMPORTING
buffer = lv_payload_x.
* My logic originally used PUT, but you should be able to change to POST
lo_http_client->request->set_method( 'PUT' ).
lo_http_client->request->set_content_type( 'application/json' ).
lo_http_client->request->set_data( lv_payload_x ).
* Sending the request
lo_http_client->send(
EXCEPTIONS
http_communication_failure = 1
http_invalid_state = 2 ).
* Receiving the response
lo_http_client->receive(
EXCEPTIONS
http_communication_failure = 1
http_invalid_state = 2
http_processing_failed = 3 ).
* Check the response. Hopefully you get back a JSON response.
DATA lv_response TYPE string.
lv_response = lo_http_client->response->get_cdata( ).
Hope this helps.
Thanks,
Brian
‎2016 Feb 10 10:26 PM
Bryan,
In my experience, if you receive a lengthy HTML string as a response when attempting to call a REST service that string usually represents some sort of HTML error page. It might be helpful to download that complete string and examine it in a notepad-like tool (or even save it to a .htm file and open it with your browser) to see if that offers more clues as to what went wrong.
If that doesn't prove to help diagnose the issue, I can offer a basic template for using the CL_HTTP_CLIENT class that is inspired by some code I wrote to link up my SAP instance with a third-party tool. There are some differences compared to your approach but hopefully this'll get help get you to finding the response you are looking for:
* Using the this CREATE_BY_URL can simplify certain aspects of using this class
DATA lo_http_client TYPE REF TO if_http_client.
cl_http_client=>create_by_url(
EXPORTING
url = 'https://rest.test.com/api/docs/doc_id'
IMPORTING
client = lo_http_client
EXCEPTIONS
argument_not_found = 1
plugin_not_active = 2
internal_error = 3
OTHERS = 4 ).
IF sy-subrc <> 0.
RETURN.
ENDIF.
* Remember to authenticate
lo_http_client->authenticate(
username = 'my-username'
password = 'my-password'
).
* Hardcode your payload here for testing purposes.
DATA lv_payload TYPE string.
lv_payload = '{"add":{"name":"my-payload"}}'.
* Convert that payload to xstring.
DATA lv_payload_x TYPE xstring.
CALL FUNCTION 'SCMS_STRING_TO_XSTRING'
EXPORTING
text = uv_payload
IMPORTING
buffer = lv_payload_x.
* My logic originally used PUT, but you should be able to change to POST
lo_http_client->request->set_method( 'PUT' ).
lo_http_client->request->set_content_type( 'application/json' ).
lo_http_client->request->set_data( lv_payload_x ).
* Sending the request
lo_http_client->send(
EXCEPTIONS
http_communication_failure = 1
http_invalid_state = 2 ).
* Receiving the response
lo_http_client->receive(
EXCEPTIONS
http_communication_failure = 1
http_invalid_state = 2
http_processing_failed = 3 ).
* Check the response. Hopefully you get back a JSON response.
DATA lv_response TYPE string.
lv_response = lo_http_client->response->get_cdata( ).
Hope this helps.
Thanks,
Brian
‎2016 Feb 10 11:01 PM
It appears our external REST service is not happy when I use cl_http_client=>create_by_destination from our SM59 destination. When I changed it to use cl_http_client=>create_by_url, it appears to be working properly.
Any ideas? I would really prefer to configure and use the SM59 destinations if I could rather than hardcoding the URLs in the code (or storing them somewhere and retrieving them but I guess I could use FILE).
Our SM59 destination looks like this:
Target Host secure.smartwaregroup.com
Path Prefix /Bigfoot/OURCOMPANY
Service No. 443
Likewise, when I call cl_http_client=>create_by_url, I pass the URL parameter as "https://secure.smartwaregroup.com/Bigfoot/OURCOMPANY/".
Either way, I then use cl_http_utility=>set_request_uri to set the request method path to "/Bigfoot/OURCOMPANY/api/methodname".
Have you ever seen differences between create_by_destination and create_by_url or differences in the way external company REST services behave?
Thanks
‎2016 Feb 10 11:58 PM
Bryan,
I believe the problem might be what is being sent to the CL_HTTP_UTILITY=>SET_REQUEST_URI. The SM59 destination is configured such that when you create your HTTP client, you're already working with an URL of secure.smartwaregroup.com/Bigfoot/OURCOMPANY. So when you set the URI with /Bigfoot/OURCOMPANY/api/methodname, you are double dipping and the web service is attempting to reach secure.smartwaregroup.com/Bigfoot/OURCOMPANY/Bigfoot/OURCOMPANY/api/methodname. I think if you just send over the '/api/methodname' it should work. If not, I can provide another example of how I got this to work on my end by using CREATE_BY_DESTINATION/
Thanks,
Brian
‎2016 Feb 11 12:11 AM
I had tried that at first, and I guess because of other issues, I thought it needed to be the full path. Now that the other issues appear to be cleared up, I changed it back and it is working now. Thank you so much for all your help!
‎2016 Feb 11 12:24 AM
So, I'm getting a response back from the /api/login method, and now I'm trying to call the /api/users method and I'm getting a status 500 Internal server error.
Do I have to close the connection and re-create the connection between each call or can I just call the cl_http_utility=>set_request_uri to change the api method path?
‎2016 Feb 11 1:37 AM
Looks like you can use the method refresh_request( ) on the CL_HTTP_CLIENT instance to reset your request object. I found that calling this method allowed me to send multiple requests on the same open HTTP client.
Here's how it works:
* Retrieving response from first request
cv_doc_info = lo_http_client->response->get_cdata( ).
* Reset request for this client
lo_http_client->refresh_request( ).
* Setup the next request
lo_http_client->request->set_method( 'GET' ).
cl_http_utility=>set_request_uri(
request = lo_http_client->request
uri = lv_new_uri
).
* Submit request next request
lo_http_client->send( ).
‎2016 Feb 11 4:49 PM
For each of my method calls, I am calling the refresh_request method right before I set my new API method URL and call SEND -- I was hoping it would work that way.
Unfortunately, even after I figure out the cause for the server error, the REST service I am trying to call requires me to place the session token returned from their login method into the HTML Authentication header under "Basic", but it appears SAP wants to keep the fields used by their authentication method separate, USERNAME and PASSWORD, and I don't see a way to edit the RAW HTML before I call the SEND method.
Might you happen to know how to set or edit the raw HTML prior to calling SEND?
Should I instead try using HTTP_POST or some other HTTP FM that might allow me to specifically control the HTML header content?
‎2016 Feb 11 8:48 PM
Ok... problems solved!!
I am now able to call multiple REST api methods. The REST service we are calling required the login session token to be passed in the HTML Authentication header, so I was able to call the http_client->request->set_header_field method with name = "Authorization" and I concatenated "Basic" with my session token (separated by space) as the value parameter.
I'm using ZJSON to convert my ABAP --> JSON before each SEND call using their zcl_json_document=>create_with_data method and from JSON --> ABAP after each RECEIVE / GET_CDATA call using their zcl_json_document=>create_with_json and get_data methods.
Thanks again for all your help!!
‎2016 Feb 11 8:53 PM
Thanks for the update! I couldn't really replicate the scenario you ran into but it's good to know how you solved it in case I ever run into it myself. Glad everything worked out, I certainly learned a lot from this exchange.
‎2019 May 24 1:08 AM
‎2019 Jul 24 6:24 PM
Hi Brian,
I would appreciate your comments about the next issue.
I have the next URL that works in Chrome: https://junjitest.mineduc.cl/api-doc-parv/v1/docente/35605/rut/17878912?apikey=SpfBRV2HHyxAyaWcorai7...
I have adapted your code as you can see:
cl_http_client=>create_by_url(
EXPORTING
url = 'https://junjitest.mineduc.cl/api-doc-parv/v1/docente/35605/rut/17878912?apikey=SpfBRV2HHyxAyaWcorai777RubbFGpYg'
IMPORTING
client = lo_http_client
EXCEPTIONS
argument_not_found = 1
plugin_not_active = 2
internal_error = 3
OTHERS = 4 ).
IF sy-subrc <> 0.
RETURN.
ENDIF.
* I've changed my logic to use GET method:
lo_http_client->request->set_method( 'GET' ).
lo_http_client->request->set_content_type( 'application/json' ).
* Sending the request
lo_http_client->send(
EXCEPTIONS
http_communication_failure = 1
http_invalid_state = 2 ).
* Receiving the response
lo_http_client->receive(
EXCEPTIONS
http_communication_failure = 1
http_invalid_state = 2
http_processing_failed = 3 ).
* Check the response. Hopefully you get back a JSON response.
DATA lv_response TYPE string.
lv_response = lo_http_client->response->get_cdata( ).And the response has been:
<!DOCTYPE HTML PUBLIC "-//IETF//DTD HTML 2.0//EN">#<html><head>#<title>404 Not Found</title>#</head><body>#<h1>Not Found</h1>#<p>The requested URL /api-doc-parv/v1/docente/35605/rut/17878912 was not found on this server.</p>#<hr>#<address>Apache/2.2.15
I'll appreciate any comments about it.
Regards,
José Jaimes
‎2022 Jan 20 8:49 PM
Hi,
I have a similar requirement and I am facing the same issue. Were you able to resolve the issue, if so please can you give me the code.