27 Temmuz

2015

Consume SOAP WebService by SAP and add custom Header

Goal

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.

Preparations
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

Pre-Dev Testing
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
In LPCONFIG,
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.

Test program
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'.
TRY.
CREATE OBJECT lr_proxy
EXPORTING
logical_port_name = '<your_lpconfig_port if not default'
.
CATCH cx_ai_system_fault into lr_ex_system.
ENDTRY.
make use of the abap pattern, it makes the life easy... (wink)
TRY.
lr_proxy-><your_ws_soapAction(
EXPORTING
<request> = ls_req
IMPORTING
<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.
ENDTRY.
exceptions for debugging...
if lr_ex_system is not INITIAL.
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( ).
endif.
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( ).
endif.
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( ).
endif.
BREAK-POINT.

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.
CONCATENATE
'<soapenv:Header>'
'<n0:UserToken xmlns:n0="http://services.provider.com/">'
'<n1:password xmlns:n1="http://webservices.provider.com">YOURpassword</n1:password>'
'<n1:username xmlns:n1="http://webservices.provider.com">YOURusername</n1:username>'
'</n0:UserToken>'
'</soapenv:Header>'
INTO l_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'
EXPORTING
xml = l_xstring
IMPORTING
document = xml_document
EXCEPTIONS
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( ).
ENDWHILE.
ENDIF.
ENDIF.

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.
other tricks:
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 . Instead, give the reference on each data element.

Sap Consume Web Service Hatası

Error in WSDL parsing: Exception occurred in library handler hatası Sap sistemine web service consume ederken çıkan bir hatadır.

Çözümü : SPROXSET transactionına girerek burdaki tabloya bir alan ekleyip

Parametre : NO_RPC_STYLE
Value : X
User : Kendi username girip kaydettikten sonra SE80’den tekrar web service hatasız olarak consume edebilirsiniz.