Application Development Discussions
Join the discussions or start your own on all things application development, including tools and APIs, programming models, and keeping your skills sharp.
cancel
Showing results for 
Search instead for 
Did you mean: 

ABAP CL_HTTP_CLIENT REST JSON issue

bryan_sippel3
Explorer

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

1 ACCEPTED SOLUTION

Former Member

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

12 REPLIES 12

Former Member

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

0 Kudos

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

0 Kudos

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

0 Kudos

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!

0 Kudos

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?

0 Kudos

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( ).

0 Kudos

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?

0 Kudos

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!!

0 Kudos

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.

0 Kudos

i get CSRF token validation failed with your code 😕

0 Kudos

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

0 Kudos

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.