We like to consume an external webservice by SAP. In other words: we should call the webservice using some given values and get the result back. We need a username+password for the webservice.
- get the WSDL file from the webservice provider
- get a set of values for calling the webservice from the webservice provider
- download and install soapUI ( http://www.soapui.org/ ), we need this for testing! If you like to access a webservice outside of your company, configure the proxy in soapUI.
- get auth.+dev.key in SAP for SE80, LPCONFIG, SMICM
We should make sure the webservice works as espected before we go into ABAP. Using soapUI,
- File > New soapUI Project
- give a project name (free to choose) and provide the WSDL file. Create everything soapUI provides (except the webTest-Case)
- in "generate testsuite", select "one testcase for each operation" and select all soapActions we require
- in "generate mock service" select also "starts the mock service immediately"
- once created, in the project tree, look for the soapAction you like to test, below "Test Steps" doubble-click on the soapAction. You should now get a new window containing two sides: the request and the response window. In the request window, fill in all "?" with the set of values you got from the webservice provider. Click run (at the top left) and look at the result in the right window. If it works, start in SAP...
Generate Enterprise Service Proxy
- in SE80, select you Package
- on the Package name, right-click and select Create > Enterprise Service
- Select "Service Consumer", Continue
- Select URL (or local file, but URL is best!), Continue
- copy the URL into the URL field, continue
- select Package, Prefix (ZWM, ZMM, ZSD... ?) and create a new Request/Task. Click Continue.
- again, continue...
Now, the Proxy should generate. If there is no error, you should see the generated proxy class. Save it and activate. Copy the generated class name.
error handling in proxy generation
If you recieve an error like ""Incorrect value: Unknown Type...":
- Execute TA SPROXSET.
- Create a new parameter NO_RPC_STYLE with your username and set the value to 'X'
- Try the proxy generation as mentioned above again.
The switch NO_RPC_STYLE might be used only if the WSDL is NOT RPC-style!
Background info: The error message you get is raised by the so called SIDL library. The sidl library is called to check wether the WSDL is document-style or rfc-style. In case of rfc-style the SIDL library is also used to handle rfc-style. Your WSDL might be document-style and can be processed without help of the SIDL library. As workaround to avoid this error message we can disable usage of the SIDL library. To achive this we have to cal TA SPROXSET and set the value X for for name NO_RPC_STYLE.
Create Local Port
- give the Proxy Class Name we copied
- Enter a reasonable Logical Port name
- tick "Default Port" (otherwise you need to give the port name each time you instantiate the proxy class)
- click "New"
- provide a description
- on tab "Call Parameters" tick and provide the Call URL. This is *NOT* the WSDL file! Most times, thats the URL just without the "?WSDL" suffix. But take a look at the WSDL file itself, usually there is an entry "address location="http://..", use this URL.
- on tab "Operations", for each operations listed, we need to find out the soapAction URL and enter it there. This information you can find in the WSDL file. Sometimes it's called "soapAction URL="http://..". but in other cases there is just the reference to the main address in the "soapAction" mentioned. In this case, use this address (NOT the WSDL file itself!).
- save and activate
- if requested, tick "State Management" in "Application-Specific Settings".
Test generate class and port
Back in SE80,
- navigate to your generated enterprise service. (Select your package > Enterprise Service > Client Proxies)
- test the class
- if you ticket "default port" in LPCONFIG, you do not need to provide the logical port name, tick "Generate Template data" and "Extended XML Handling"
- now you should see some generated XML, on the top, click edit.
- enter your test values
- run it
If you are lucky, you see the result... But if you are not able to enter all required data for the test (e.g. username+password, a session-id etc.), you should get an empty result or an exception. In this case, it gets a bit complicated as SAP is not able to handle header entries by itself. Read further down for the solution.
While proxy generation, not only the proxy is created - also the relevant datatypes. So you need to find out which ones are used. In this example, I assume we have an importing structure (workarea) and one exporting (internal) table. Also, there is at least one exception class generated. Please update this code example if you find easier ways, also the exception handling is not yet really useful.
DATA: lr_proxy TYPE REF TO z<your_client_proxy_class>,
lr_ex_service TYPE REF TO z<your_exception_class>,
lr_ex_system type ref to cx_ai_system_fault,
lr_ex_application type ref to cx_ai_application_fault,
DATA: ls_req TYPE z<your_request_structure>
lt_ret TYPE z<your_return_table>,
ls_ret TYPE z<your_return_table_linetype>.
DATA: lv_ex1 TYPE string,
lv_ex2 TYPE string,
lv_ex3 TYPE string.
ls_req-var1 = 'foo'.
ls_req-var2 = 'bar'.
CREATE OBJECT lr_proxy
- logical_port_name = '<your_lpconfig_port if not default'
CATCH cx_ai_system_fault into lr_ex_system.
- make use of the abap pattern, it makes the life easy...
<request> = ls_req
<return> = lt_ret
CATCH cx_ai_system_fault into lr_ex_system.
CATCH z<your_exception_class> INTO lr_ex_service.
CATCH cx_ai_application_fault into lr_ex_application.
- exceptions for debugging...
if lr_ex_system is not INITIAL.
- exceptions for debugging...
lv_ex1 = lr_ex_system->if_message~get_text( ).
lv_ex2 = lr_ex_system->if_message~get_longtext( ).
*lv_ex3 = lr_ex->if_ai_application_fault~get_rt_fault_text( ).
if lr_ex_service is not INITIAL.
lv_ex1 = lr_ex_service->if_message~get_text( ).
lv_ex2 = lr_ex_service->if_message~get_longtext( ).
lv_ex3 = lr_ex_service->if_ai_application_fault~get_rt_fault_text( ).
if lr_ex_application is not INITIAL.
lv_ex1 = lr_ex_application->if_message~get_text( ).
lv_ex2 = lr_ex_application->if_message~get_longtext( ).
lv_ex3 = lr_ex_application->if_ai_application_fault~get_rt_fault_text( ).
If you are lucky ;) you should get the result in your lt_res table.
See whats sent/recieved
You can see what is sent/recieved using SMICM.
- Goto > Trace Level > Set. Enter value '3', ok.
- Goto > Trace File > Reset.
- run your test programm
- Goto > Trace File > Show all.
- do not forget to set the trace level back to 1.
The POST / GET is shown in HEX and a in a small text area. You can export the whole trace file and open it in a professional text editor (such like ultraedit or similar). Use column mode to select the text, copy it into a new file, remove all line-breaks and replace "><" with ">_<" (_ represents line-break). Then you could read the get/response easier.
That way, you could also copy the generated POST xml into soapUI and play around with the values, structure etc. if the request fails. (remove the whole HTML header out of the trace before)
Add custom header
Sometimes you need to add a header to the SOAP message, e.g. a username and password, a session-id, an api-key etc. Unlike in other programming frameworks where such values are added to the generated class constructors, SAP does not support them... unfortunately. But there is a tricky way to add a custom header.
The class documentation from SAP: http://help.sap.com/saphelp_nw70ehp1/helpdata/en/47/3d95b07e1a20cae10000000a155369/frameset.htm
Using the IF_WSPROTOCOL* we have also access to the WS_HEADER. But we can not easily add a header, we need to use method set_request_header of the interface-class if_wsprotocol_ws_header. This one does not take just a string, but requires you to provide the xml-name, xml-namespace and the xml-element as a dom element. To create this value out of a given xml-header, we have to use FM SDIXML_XML_TO_DOM.
All this we need to do before we call the soapAction.
Example code: DATA: ixml TYPE REF TO if_ixml,
xml_document TYPE REF TO if_ixml_document,
xml_root TYPE REF TO if_ixml_element,
xml_element TYPE REF TO if_ixml_element,
xml_node TYPE REF TO if_ixml_node.
DATA l_xstring TYPE xstring.
DATA l_string TYPE string.
DATA: name TYPE string,
namespace TYPE string.
- convert to xstring
l_xstring = cl_proxy_service=>cstring2xstring( l_string ).
IF l_string IS NOT INITIAL.
- create ixml dom document from xml xstring
CALL FUNCTION 'SDIXML_XML_TO_DOM'
xml = l_xstring
document = xml_document
invalid_input = 1
OTHERS = 2.
IF sy-subrc = 0 AND xml_document IS NOT INITIAL.
xml_root = xml_document->get_root_element( ).
xml_element ?= xml_root->get_first_child( ).
- add header element by element to soap header
WHILE xml_element IS NOT INITIAL.
name = xml_element->get_name( ).
namespace = xml_element->get_namespace_uri( ).
lr_header_protocol->set_request_header( name = name namespace = namespace dom = xml_element ).
xml_element ?= xml_element->get_next( ).
The common problems here are:
- SDIXML_XML_TO_DOM needs to be successful, otherwise the header is not added. The main reason for errors from this FM is a malformed XML header you provided. Test your header in soapUI before copy into the concatenate. If it still fails, reformat XML until it works. This is maybe hard and time-consuming. Get some good coffee and music, play around until it works... notice, that just a missing slash (/) in the XML can ruin your result.
- is the FM is successful, look at the exceptions thrown while sending the soap request (we catch them at the end of the test programm)
- the returned exception texts usually do not tell you much. Use SMICM to look at the response.
- if you send the header successfully, but the webservice does not accept your username+password (but should, because in soapUI it does...) the namespace names (xmlns:) might be different. Make sure lower/uppercase is correct, watch out for slahes, spaces and so on.
- make sure your reference names do not contain special chars, preferably use short reference names (n0, n1...)
- it looks not sufficient if you provide the reference information (xmlns:...) in the <soapenv:Header xmlns..>. Instead, give the reference on each data element.