SOAP WSDL: Unterschied zwischen den Versionen
Cg (Diskussion | Beiträge) |
Cg (Diskussion | Beiträge) |
||
(38 dazwischenliegende Versionen von 2 Benutzern werden nicht angezeigt) | |||
Zeile 1: | Zeile 1: | ||
== SOAP WSDL Package == |
== SOAP WSDL Package == |
||
This document describes the internal workings of the SOAP framework (in the Smalltalk class library). The SOAP actions call these internal functions, and the WSDL importer generates code to call them. |
|||
As an expecco user, you will not need to know those details; |
|||
however the information herein may be useful if you have very special/exotic transport requirements or to setup fake (mockup) services and WSDL providers. |
|||
== Basic Usage == |
== Basic Usage == |
||
These chapter describes the basic underlying implementation. |
These chapter describes the basic underlying implementation. |
||
The code generated by the WSDL class generator calls those functions. |
The code generated by the WSDL class generator calls those functions. |
||
To make a SOAP request as a client, given a WSDL (either as file, string or URL), |
To make a SOAP request as a client, given a WSDL (either as file, string or URL), |
||
Zeile 24: | Zeile 28: | ||
this is the preferred method, especially as it will automatically deal with imported schema definitions |
this is the preferred method, especially as it will automatically deal with imported schema definitions |
||
(i.e. if the WSDL refers to other documents via an import) |
(i.e. if the WSDL refers to other documents via an import) |
||
<CODE><PRE> |
|||
service := SprayWSDLService onUrl: 'anUrlString' |
service := SprayWSDLService onUrl: 'anUrlString' |
||
</PRE></CODE> |
|||
====== From a String ====== |
====== From a String ====== |
||
Zeile 33: | Zeile 34: | ||
this only works, if the WSDL is self contained (eg. contains all required definitions and does not import other documents). |
this only works, if the WSDL is self contained (eg. contains all required definitions and does not import other documents). |
||
If it does, missing documents will be fetched automatically via HTTP (see description on transports below). |
If it does, missing documents will be fetched automatically via HTTP (see description on transports below). |
||
<CODE><PRE> |
|||
service := SprayWSDLService onXmlString: 'wsdlString' |
service := SprayWSDLService onXmlString: 'wsdlString' |
||
</PRE></CODE> |
|||
====== From a Set of Files/Strings ====== |
====== From a Set of Files/Strings ====== |
||
Zeile 46: | Zeile 44: | ||
and delivers that contents, when asked for (i.e. it simulates an SptHTTPClient-transport). |
and delivers that contents, when asked for (i.e. it simulates an SptHTTPClient-transport). |
||
Thus, if you have a WSDL document, which imports other documents, AND you want to prevent WSDL fetches via |
Thus, if you have a WSDL document, which imports other documents, AND you want to prevent WSDL fetches via HTTP of your program at runtime, you should setup an instance of SptHTTPLocalTransport, give it all the documents (incl. any imported docs), |
||
of your program at runtime, you should setup an instance of SptHTTPLocalTransport, give it all the documents (incl. any imported docs), |
|||
and provide that as a document retriever. |
and provide that as a document retriever. |
||
For example, if you have a WSDL in (urlA), which imports urlB and urlC, |
For example, if you have a WSDL in (urlA), which imports urlB and urlC, |
||
create a transport which contains all three documents as: |
create a transport which contains all three documents as: |
||
<CODE><PRE> |
|||
localTransport := SptHTTPLocalTransport new. |
localTransport := SptHTTPLocalTransport new. |
||
localTransport localDocuments |
localTransport localDocuments |
||
at: urlA "eg something like: 'http://foo.services.de:30050/partner/PartnerBusinessService?SCHEMA'" |
at: urlA "eg something like: 'http://foo.services.de:30050/partner/PartnerBusinessService?SCHEMA'" |
||
put: |
put: |
||
'<?xml version="1.0"?> |
'<?xml version="1.0"?> |
||
... the whole urlA document as string... |
... the whole urlA document as string... |
||
'. |
'. |
||
localTransport localDocuments |
localTransport localDocuments |
||
at: urlB "eg something like: 'http://foo.services.de..." |
at: urlB "eg something like: 'http://foo.services.de..." |
||
put: |
put: |
||
'<?xml version="1.0"?> |
'<?xml version="1.0"?> |
||
... the whole urlB document as string... |
... the whole urlB document as string... |
||
'. |
'. |
||
localTransport localDocuments |
localTransport localDocuments |
||
at: urlC "eg something like: 'http://foo.services.de..." |
at: urlC "eg something like: 'http://foo.services.de..." |
||
put: |
put: |
||
'<?xml version="1.0"?> |
'<?xml version="1.0"?> |
||
... the whole urlC document as string... |
... the whole urlC document as string... |
||
'. |
'. |
||
</PRE></CODE> |
|||
and then create the service with: |
and then create the service with: |
||
definitions := WSDLDefinitions |
|||
<CODE><PRE> |
|||
definitions := WSDLDefinitions |
|||
onUrl: urlA |
onUrl: urlA |
||
transport: localTransport. |
transport: localTransport. |
||
service := SprayWSDLService onDefinitions: definitions. |
service := SprayWSDLService onDefinitions: definitions. |
||
In such a setup, all required import URLs will be fetched from there (e.g. no HTTP requests required). |
|||
</PRE></CODE> |
|||
In such a setup, all required import urls will be fetched from there (eg. no HTTP requests required). |
|||
You can provide the |
You can provide the URL contents from class variables, class getter methods, etc. |
||
Of course, you can also read the documents from a local file, and setup the localTransport instance |
Of course, you can also read the documents from a local file, and setup the localTransport instance |
||
from their contents. |
from their contents. |
||
Zeile 92: | Zeile 85: | ||
All of the above methods require some initial processing time (in the order of a few 100ms), |
All of the above methods require some initial processing time (in the order of a few 100ms), |
||
because they read the textual WSDL with the XML parser, and construct a |
because they read the textual WSDL with the XML parser, and construct a rather complicated object structure containing those definitions. (most of the space is taken up by XML-schema definitions, which are kept as XML-node trees). |
||
The original Dolphin implementation used Smalltalk binary object storage to speed this up. |
The original Dolphin implementation used Smalltalk binary object storage to speed this up. |
||
Zeile 107: | Zeile 100: | ||
To create a client, use either: |
To create a client, use either: |
||
<CODE><PRE> |
|||
client := service createClient |
client := service createClient |
||
</PRE></CODE> |
|||
which creates a client to the service's default host (which is the one specified in the WSDL). |
which creates a client to the service's default host (which is the one specified in the WSDL). |
||
Zeile 116: | Zeile 107: | ||
Then use: |
Then use: |
||
<CODE><PRE> |
|||
client := service createClientTo:'http://foo.bar.com:4933/bla/...'. |
client := service createClientTo:'http://foo.bar.com:4933/bla/...'. |
||
</PRE></CODE> |
|||
==== Create a Call Object with Call Arguments ==== |
==== Create a Call Object with Call Arguments ==== |
||
Zeile 127: | Zeile 116: | ||
Call argument objects fall into two categories: |
Call argument objects fall into two categories: |
||
* simple objects (xsd:simpleTypes, which can be |
* simple objects (xsd:simpleTypes, which can be directly mapped to Smalltalk objects) |
||
* complex objects (in the XML schema, these are xsd:complexType elements) |
* complex objects (in the XML schema, these are xsd:complexType elements) |
||
Zeile 144: | Zeile 133: | ||
Complex types must be represented as instances of one of: |
Complex types must be represented as instances of one of: |
||
* an XeQstruct - this is a special kind of Dictionary, which uses qualified names ( |
* an XeQstruct - this is a special kind of Dictionary, which uses qualified names (e.g. XML names with namespace and prefix) as keys. This is the default representation used by the SOAP framework. |
||
* a Dictionary - if values are given as dictionary instance, it must contain key-value mappings for the sub-elements. The keys are the localName of the schema's element names (i.e. if the element is named foo:someType, then the key should be 'foo' only). |
* a Dictionary - if values are given as dictionary instance, it must contain key-value mappings for the sub-elements. The keys are the localName of the schema's element names (i.e. if the element is named foo:someType, then the key should be 'foo' only). |
||
* a special data holder object - this must understand getter- and setter messages, corresponding to the local names of the elements. |
* a special data holder object - this must understand getter- and setter messages, corresponding to the local names of the elements. |
||
A concrete example is found in the BLZ-testcase: |
A concrete example is found in the BLZ-testcase: |
||
<CODE><PRE> |
|||
call := client send: 'getBank' withArguments:getBankArg. |
call := client send: 'getBank' withArguments:getBankArg. |
||
</PRE></CODE> |
|||
==== Performing the Service Call ==== |
==== Performing the Service Call ==== |
||
Finally, execute the call by giving it a #value message (think of the call object as a block, which does the call for you): |
Finally, execute the call by giving it a #value message (think of the call object as a block, which does the call for you): |
||
<CODE><PRE> |
|||
result := call value. |
result := call value. |
||
</PRE></CODE> |
|||
this harmless looking message performs the transfer. It sets up the communication (usually HTTP, where the SOAP message is wrapped into an HTTP-request), sends the message in an asynchronous send process, awaits the response, decodes the returned XML and instantiates the result object(s). |
this harmless looking message performs the transfer. It sets up the communication (usually HTTP, where the SOAP message is wrapped into an HTTP-request), sends the message in an asynchronous send process, awaits the response, decodes the returned XML and instantiates the result object(s). |
||
Zeile 164: | Zeile 149: | ||
Notice that SOAP allows for multiple return values to be specified in the WSDL. |
Notice that SOAP allows for multiple return values to be specified in the WSDL. |
||
If this is the case, the result returned by #value is the first return value, |
If this is the case, the result returned by #value is the first return value, |
||
and a sequenceable collection of all return values is retrieved via |
and a sequenceable collection of all return values is retrieved via "<code>call outParameters</code>". |
||
==== Debugging / Logging HTTP Traffic ==== |
|||
Take a look at "UserPreferences" and the <code>SOAP__SptHTTPRequest's Verbose</code> class variable. See the chapter at the end on logging flags. |
|||
A call object's in/out XML can be inspected (after the call) with: |
|||
call in xml inspect. |
|||
call out xml inspect. |
|||
== WSDL Class Generator == |
== WSDL Class Generator == |
||
The wsdl class generator takes WSDL definitions and generates a client class |
The wsdl class generator takes WSDL definitions and generates a Smalltalk client class. This contains entries for the operations and type classes which hold the arguments and return values. |
||
==== Generating Classes ==== |
==== Generating Classes ==== |
||
Zeile 174: | Zeile 166: | ||
First, instantiate the WSDLClassBuilder. |
First, instantiate the WSDLClassBuilder. |
||
You need either a service instance (see above) or a |
You need either a service instance (see above) or a WSDL definitions object, as argument to the instantiation message. |
||
For example: |
For example: |
||
<CODE><PRE> |
|||
service := SprayWSDLService onXmlString:'... a lot of WSDL here ...' |
service := SprayWSDLService onXmlString:'... a lot of WSDL here ...' |
||
builder := WSDLClassBuilder service: service |
builder := WSDLClassBuilder service: service |
||
</PRE></CODE> |
|||
or: |
or: |
||
<CODE><PRE> |
|||
definitions := WSDLDefinitions onURL:'http://.....?wsdl'. |
definitions := WSDLDefinitions onURL:'http://.....?wsdl'. |
||
builder := WSDLClassBuilder definitions: definitions |
builder := WSDLClassBuilder definitions: definitions |
||
</PRE></CODE> |
|||
Then let it generate the classes with: |
Then let it generate the classes with: |
||
<CODE><PRE> |
|||
builder generate |
builder generate |
||
</PRE></CODE> |
|||
Unless overwritten by the setup messages below, |
Unless overwritten by the setup messages below, |
||
Zeile 198: | Zeile 184: | ||
Data objects are generated for all non-simpleType objects, which are recursively reached/used by the WSDL operations, |
Data objects are generated for all non-simpleType objects, which are recursively reached/used by the WSDL operations, |
||
starting with the individual operation arguments. |
starting with the individual operation arguments. |
||
Datatype class names are constructed in a similar fashion: a "SOAP__" prefix, the service name, an underline, and the type name from the |
Datatype class names are constructed in a similar fashion: a "SOAP__" prefix, the service name, an underline, and the type name from the XSD-schema. |
||
For example, if the WSDL contains an |
For example, if the WSDL contains an XSD type named "bestaetigePartner", the generated type class would be named "SOAP__PartnerService_bestaetigePartner". |
||
the generated type class would be named "SOAP__PartnerService_bestaetigePartner". |
|||
The service name is included, to avoid a name conflict with type classes from other services. |
The service name is included, to avoid a name conflict with type classes from other services. |
||
Zeile 209: | Zeile 194: | ||
===== clientClassName: aString ===== |
===== clientClassName: aString ===== |
||
defines (overwrites) the name of the generated client class. Thus, if you say: |
defines (overwrites) the name of the generated client class. Thus, if you say: |
||
<CODE><PRE> |
|||
builder clientClassName: 'DBK_PService'. |
builder clientClassName: 'DBK_PService'. |
||
</PRE></CODE> |
|||
the generated client class will be named "DBK_PService" instead of "SOAP_PartnerServiceClient" |
the generated client class will be named "DBK_PService" instead of "SOAP_PartnerServiceClient" |
||
Zeile 217: | Zeile 200: | ||
===== typeClassNamePrefix: aString ===== |
===== typeClassNamePrefix: aString ===== |
||
defines (overwrites) the prefix to be used for type classes. This includes the namespace prefix. Thus, if you say: |
defines (overwrites) the prefix to be used for type classes. This includes the namespace prefix. Thus, if you say: |
||
<CODE><PRE> |
|||
builder typeClassNamePrefix: 'DBK_'. |
builder typeClassNamePrefix: 'DBK_'. |
||
</PRE></CODE> |
|||
all generated type classes will be named "DBK_<schema name>". |
all generated type classes will be named "DBK_<schema name>". |
||
Zeile 225: | Zeile 206: | ||
===== classNamePrefix: aString ===== |
===== classNamePrefix: aString ===== |
||
defines (overwrites) the prefix to be used for all classes (i.e. type- and client classes). This includes the namespace prefix. Thus, if you say: |
defines (overwrites) the prefix to be used for all classes (i.e. type- and client classes). This includes the namespace prefix. Thus, if you say: |
||
<CODE><PRE> |
|||
builder classNamePrefix: 'DBK_'. |
builder classNamePrefix: 'DBK_'. |
||
all generated classes will be named "DBK_<schema name>", and the client class will be "DBK_<serviceName>Client". |
|||
</PRE></CODE> |
|||
all generated type classes will be named "DBK_<schema name>", and the client class will be "DBK_<serviceName>Client". |
|||
==== Generated Data Holder Classes (TypeClasses) ==== |
|||
For each used XSD type, a corresponding Smalltalk class is created. |
|||
These are (and should be) complete passive objects which exist only as data holders. |
|||
Their instance variable structure and getter/setter interface directly corresponds to the element-structure of the corresponding XSD types. |
|||
However, all element names are converted to lowercaseFirst (unless the forceLowercaseFieldNames option is set to false, as described below). |
|||
Also, multiple occurs fields are given a name with a trailing underscore ("_"), and an additional adder-method is generated. |
|||
I.e. for a multiple-occurs element named "foo", a getter named "foo_" (which returns the multiple elements as an array), a setter named "foo_:", which expects the multiple values as an array), and an adder "add_foo:" (which expects a single object to be added) are generated. |
|||
The translation to lowercase is done, because most Smalltalks give a warning about or even forbid instance variables with an uppercase first character. However, you may prefer having the original XSD schema names as instance variables and getter/setter names. |
|||
Then use: |
|||
builder forceLowercaseFieldNames: false |
|||
before calling the generate method. |
|||
==== Using Generated Client Classes ==== |
==== Using Generated Client Classes ==== |
||
Zeile 239: | Zeile 233: | ||
The generated methods are written to include comments and naming hints (argument names) which will help in instantiating the required argument objects correctly. |
The generated methods are written to include comments and naming hints (argument names) which will help in instantiating the required argument objects correctly. |
||
The default destination |
The default destination URL is returned by the "destinationUrl" method. By default, this returns the URL as found in the original WSDL. |
||
A setter method "destinationUrl:" allows for this default to be overwritten. |
A setter method "destinationUrl:" allows for this default to be overwritten. |
||
To instantiate a service client, use: |
To instantiate a service client, use: |
||
<CODE><PRE> |
|||
serviceClient := <generatedServiceClientClass> new. |
serviceClient := <generatedServiceClientClass> new. |
||
</PRE></CODE> |
|||
to use a different URL, change it with: |
to use a different URL, change it with: |
||
<CODE><PRE> |
|||
serviceClient destinationUrl: 'http://...'. |
serviceClient destinationUrl: 'http://...'. |
||
</PRE></CODE> |
|||
Once instantiated, the serviceClient can be reused for multiple requests to the same service host. |
Once instantiated, the serviceClient can be reused for multiple requests to the same service host. |
||
It does not keep connections open between service calls (unless your underlying HTTP interface does so, and the HTTP connection was opened with the " |
It does not keep connections open between service calls (unless your underlying HTTP interface does so, and the HTTP connection was opened with the "connection: keep" option - which is not done by default). |
||
Then setup the service arguments. Look into the source of the client classes' operation methods, to see what it expects, |
Then setup the service arguments. Look into the source of the client classes' operation methods, to see what it expects, |
||
and the source of the corresponding type-class to see what elements it has: |
and the source of the corresponding type-class to see what elements it has: |
||
<CODE><PRE> |
|||
arg := <operationTypeClass> new. |
arg := <operationTypeClass> new. |
||
arg foo: valueForFoo. |
arg foo: valueForFoo. |
||
arg bar: valueForBar. |
arg bar: valueForBar. |
||
... |
... |
||
</PRE></CODE> |
|||
Finally, perform the service call with: |
Finally, perform the service call with: |
||
<CODE><PRE> |
|||
rslt := serviceClient operation: arg. |
rslt := serviceClient operation: arg. |
||
the returned result-object will be an instance of the response type-class, with getters as specified in the WSDL. Look at the comments in the getters, to see what you get. |
|||
</PRE></CODE> |
|||
the resturned result-object will be an instance of the response type-class, |
|||
with getters as specified in the WSDL. Look at the comments in the getters, to see what you get. |
|||
==== Instantiating Argument Objects ==== |
==== Instantiating Argument Objects ==== |
||
The generated data holding type classes will have getters and setters for their elements. |
The generated data holding type classes will have getters and setters for their elements. |
||
If the schema includes restrictions (such as a length restriction on a string, or an enumeration of allowed values), |
If the schema includes restrictions (such as a length restriction on a string, or an enumeration of allowed values), the setters will also perform verification of the argument. |
||
the setters will also perform verification of the argument. |
|||
In addition, the generated code includes comments, which describe any restrictions. |
In addition, the generated code includes comments, which describe any restrictions. |
||
Zeile 284: | Zeile 268: | ||
The generated client class has an instance variable, which holds the previous request object. |
The generated client class has an instance variable, which holds the previous request object. |
||
This can be inspected in case of an error. For example, |
This can be inspected in case of an error. For example, |
||
<CODE><PRE> |
|||
serviceClient lastRequest out xml |
serviceClient lastRequest out xml |
||
</PRE></CODE> |
|||
returns the last out-going request's full XML envelope, |
returns the last out-going request's full XML envelope, |
||
<br>and: |
<br>and: |
||
<CODE><PRE> |
|||
serviceClient lastRequest in xml |
serviceClient lastRequest in xml |
||
</PRE></CODE> |
|||
contains the last response. |
contains the last response. |
||
Zeile 300: | Zeile 280: | ||
Generating the partnerService classes: |
Generating the partnerService classes: |
||
definitions := WSDLDefinitions |
|||
<CODE><PRE> |
|||
definitions := WSDLDefinitions |
|||
onUrl: 'http://plan-eval-esb.services.debeka.de:30050/partner/proxyservice/PartnerBusinessService?WSDL'. |
onUrl: 'http://plan-eval-esb.services.debeka.de:30050/partner/proxyservice/PartnerBusinessService?WSDL'. |
||
builder := WSDLClassBuilder definitions:definitions. |
builder := WSDLClassBuilder definitions:definitions. |
||
builder generate. |
builder generate. |
||
</PRE></CODE> |
|||
this will generate the following classes (into the class category "SOAP-Generated-partnerService"): |
this will generate the following classes (into the class category "SOAP-Generated-partnerService"): |
||
Zeile 323: | Zeile 300: | ||
To call the partnerService at its default service port address, |
To call the partnerService at its default service port address, |
||
use: |
use: |
||
<CODE><PRE> |
|||
service := SOAP__PartnerService new. |
service := SOAP__PartnerService new. |
||
suchArg := SOAP_PartnerService_seitenSuche new. |
suchArg := SOAP_PartnerService_seitenSuche new. |
||
suchArg ergebnisType:'ALLES'. |
suchArg ergebnisType:'ALLES'. |
||
result := service listePartner: suchArg. |
result := service listePartner: suchArg. |
||
"result will be an instance of kontrollTyp" |
"result will be an instance of kontrollTyp" |
||
Transcript show: result code. |
Transcript show: result code. |
||
Transcript show: result fehler. |
Transcript show: result fehler. |
||
Transcript show: result information. |
Transcript show: result information. |
||
</PRE></CODE> |
|||
for a different service URL (in-house test service), use: |
for a different service URL (in-house test service), use: |
||
<CODE><PRE> |
|||
service := SOAP__PartnerService new. |
service := SOAP__PartnerService new. |
||
service destinationUrl:'http://testServer:8080/partner/proxyservice/PartnerBusinessService. |
service destinationUrl:'http://testServer:8080/partner/proxyservice/PartnerBusinessService. |
||
... |
... |
||
</PRE></CODE> |
|||
== SOAP Package Structure == |
== SOAP Package Structure == |
||
Zeile 353: | Zeile 325: | ||
Therefore, in ST/X code, you will find class references without prefix. |
Therefore, in ST/X code, you will find class references without prefix. |
||
For VSE, which has no namespaces, class names are translated by prefixing the name with "<namespace>__". Thus all classes will have a "SOAP__" prefix in VSE. This is automatically done by the STX-VSE class exporter, which was |
For VSE, which has no namespaces, class names are translated by prefixing the name with "<namespace>__". Thus all classes will have a "SOAP__" prefix in VSE. This is automatically done by the STX-VSE class exporter, which was developed specifically for this purpose. |
||
Notice, that this automatic rewrite can of course not detect situations where class names are constructed dynamically, |
Notice, that this automatic rewrite can of course not detect situations where class names are constructed dynamically, |
||
and referred to via "Smalltalk at:". Such code had to be identified and manually changed to deal with both dialects. |
and referred to via "Smalltalk at:". Such code had to be identified and manually changed to deal with both dialects. |
||
Usually, you will find code like "Smalltalk isSmalltalkX ifTrue:[...]" or "Smalltalk isVisualSmalltalkEnterprise ifTrue:[...]" in those parts. The goal is to keep a common code base, so that future improvements and fixes will be immediately available in both dialects. |
Usually, you will find code like "Smalltalk isSmalltalkX ifTrue:[...]" or "Smalltalk isVisualSmalltalkEnterprise ifTrue:[...]" in those parts. The goal is to keep a common code base, so that future improvements and fixes will be immediately available in both dialects. |
||
===== Package: xe ("stx_goodies_soap_xe") ===== |
===== Package: xe ("stx_goodies_soap_xe") ===== |
||
contains classes to represent qualified names, |
contains classes to represent qualified names, XSD schema definitions, XSD typespaces and mappings etc. |
||
there are no input/output or GUI related operations in this package. |
there are no input/output or GUI related operations in this package. |
||
Most of the classes found there can be mapped one-by-one to corresponding elements from an |
Most of the classes found there can be mapped one-by-one to corresponding elements from an XSD schema definition. |
||
Object hierarchies of them are created by the WSDL readers, the SOAP envelope creators and the SOAP encoders. |
Object hierarchies of them are created by the WSDL readers, the SOAP envelope creators and the SOAP encoders. |
||
|
|
||
Zeile 384: | Zeile 355: | ||
===== Package: soap ("stx_goodies_soap") ===== |
===== Package: soap ("stx_goodies_soap") ===== |
||
contains all the rest, such as error classes, abstract classes, encoders/decoders, transport interfaces (adapters to HTTP), etc. |
contains all the rest, such as error classes, abstract classes, encoders/decoders, transport interfaces (adapters to HTTP), etc. |
||
===== Package: auth ("stx_goodies_soap_spray_auth") ===== |
|||
additional classes which deal with authentication. This is currently not maintained, and provided "as-is". No effort has been made in porting or verifying its usefulness. However, it seems to compile (at least) in VSE. |
|||
Exept does not currently use these classes in any of its projects - we recommend using regular SOAP over a secure SSH (HTTPS) connection. Then this package will not be needed at all. |
|||
== Classes of Special Interest == |
== Classes of Special Interest == |
||
==== SOAP__SptHTTPRequest ==== |
==== SOAP__SptHTTPRequest ==== |
||
performs the actual HTTP get operation for both WSDL fetching AND the actual SOAP call. |
performs the actual HTTP get operation for both WSDL fetching AND the actual SOAP call. |
||
The entry is "send:" which dispatches to either "send_VSE" or "send_NonVSE" (for ST/X). |
The entry is "send:" which dispatches to either "send_VSE" or "send_NonVSE" (for ST/X). |
||
Zeile 397: | Zeile 371: | ||
the basic call entry. By placing a halt on the "value" method, you can single step through the send/receive process. |
the basic call entry. By placing a halt on the "value" method, you can single step through the send/receive process. |
||
In case of decoding errors (wrong XML/WSDL), it provides the raw XML response via additional getter methods (try #inXmlOrEmpty). |
In case of decoding errors (wrong XML/WSDL), it provides the raw XML response via additional getter methods (try #inXmlOrEmpty). |
||
Also, in case of xsd:schema errors (wrong restrictions etc.), less strict |
Also, in case of xsd:schema errors (wrong restrictions etc.), less strict API entries (extractResuming) are provided which may be able to decode a response even if erroneous. |
||
==== SOAP__WSDLClassBuilder ==== |
|||
the new builder class, which was not part of the original SOAP package. Generates the client class, enumerates all reachable non-simple type classes and uses the XeClassBuilder to generate type classes. |
|||
XeClassBuilder used to be present in the original (2010 SOAP) version, but has been much enhanced to generate reasonable argument and type names, comments and verification code. Also, array handling and restricted simple type handling was added. |
|||
==== UserPreferences ==== |
|||
In ST/X, the "UserPreferences current" holds all settings of the current user session. |
|||
In VSE, a mimicri interface is provided for compatibility. |
|||
This provides some debugging/logging flags of interest: |
|||
* logHTTPRequests<br>to enable/disable logging of HTTP traffic on the Transcript. Set to true during development, false for deployed applications. The SOAP__SptHTTPRequest also contains a private Verbose class variable, which can be also set to true (nil counts as false). |
|||
* soapLoggingLevel<br>controls how much debugging info is emitted from SOAP itself. |
|||
* soapErrorDebugging<br>if this returns true, you will get a debugger whenever a SOAP-internal error occurs. Otherwise, the error might be reported as a SOAPFault. |
Aktuelle Version vom 8. Juli 2023, 04:55 Uhr
Inhaltsverzeichnis
SOAP WSDL Package[Bearbeiten]
This document describes the internal workings of the SOAP framework (in the Smalltalk class library). The SOAP actions call these internal functions, and the WSDL importer generates code to call them.
As an expecco user, you will not need to know those details; however the information herein may be useful if you have very special/exotic transport requirements or to setup fake (mockup) services and WSDL providers.
Basic Usage[Bearbeiten]
These chapter describes the basic underlying implementation. The code generated by the WSDL class generator calls those functions.
To make a SOAP request as a client, given a WSDL (either as file, string or URL), you have to:
- create a service from the WSDL
- instantiate a client
- create a call object with call arguments
- perform the call
- extract the values from the returned result object
Creating a Service Instance[Bearbeiten]
The service instance keeps the information about the available entry points, the protocol and encoding and the data types. You need a WSDL to create a service. This can come from multiple sources:
From URL (preferred)[Bearbeiten]
this is the preferred method, especially as it will automatically deal with imported schema definitions (i.e. if the WSDL refers to other documents via an import)
service := SprayWSDLService onUrl: 'anUrlString'
From a String[Bearbeiten]
this only works, if the WSDL is self contained (eg. contains all required definitions and does not import other documents). If it does, missing documents will be fetched automatically via HTTP (see description on transports below).
service := SprayWSDLService onXmlString: 'wsdlString'
From a Set of Files/Strings[Bearbeiten]
when a WSDL URL is parsed, a transport object instance is used to fetch imported documents. By default, an instance of SptHTTPClient is used to fetch required documents.
A mock transport class named SptHTTPLocalTransport can be used. This keeps a list of string documents in a dictionary mapped by URL and delivers that contents, when asked for (i.e. it simulates an SptHTTPClient-transport).
Thus, if you have a WSDL document, which imports other documents, AND you want to prevent WSDL fetches via HTTP of your program at runtime, you should setup an instance of SptHTTPLocalTransport, give it all the documents (incl. any imported docs), and provide that as a document retriever.
For example, if you have a WSDL in (urlA), which imports urlB and urlC, create a transport which contains all three documents as:
localTransport := SptHTTPLocalTransport new. localTransport localDocuments at: urlA "eg something like: 'http://foo.services.de:30050/partner/PartnerBusinessService?SCHEMA'" put: '<?xml version="1.0"?> ... the whole urlA document as string... '. localTransport localDocuments at: urlB "eg something like: 'http://foo.services.de..." put: '<?xml version="1.0"?> ... the whole urlB document as string... '. localTransport localDocuments at: urlC "eg something like: 'http://foo.services.de..." put: '<?xml version="1.0"?> ... the whole urlC document as string... '.
and then create the service with:
definitions := WSDLDefinitions onUrl: urlA transport: localTransport. service := SprayWSDLService onDefinitions: definitions.
In such a setup, all required import URLs will be fetched from there (e.g. no HTTP requests required).
You can provide the URL contents from class variables, class getter methods, etc. Of course, you can also read the documents from a local file, and setup the localTransport instance from their contents.
From Binary Storage[Bearbeiten]
All of the above methods require some initial processing time (in the order of a few 100ms), because they read the textual WSDL with the XML parser, and construct a rather complicated object structure containing those definitions. (most of the space is taken up by XML-schema definitions, which are kept as XML-node trees).
The original Dolphin implementation used Smalltalk binary object storage to speed this up. Definition objects were converted to a binary store bytes at development time, those kept in a class variable in the deployed end-user image and restored from it at execution time.
Be aware, that binary storage data is both system dependent and hard to analyze in case of errors. Our experience is that it is worth to have the WSDL around in plain text, as fixes are much easier to make there, in case of problems.
Instantiating a Client[Bearbeiten]
Once you have the service object, you need a client to a concrete partner service. This client prepares the outgoing and incoming envelopes, to save some processing time in the actual message send.
To create a client, use either:
client := service createClient
which creates a client to the service's default host (which is the one specified in the WSDL).
Often, this is not the one you want to connect to (for example, if you have a local test service running, and/or the host address of the service is different for in-house connections). Also, some WSDLs do not include a valid service URL.
Then use:
client := service createClientTo:'http://foo.bar.com:4933/bla/...'.
Create a Call Object with Call Arguments[Bearbeiten]
Finally, an actual service call is to be performed.
This is a two-step operation: first, a call instance is created, which takes the name of the operation and its arguments, and generates an out-envelope (which includes the XML representation of the arguments, as specified in the WSDL's encoding information). The second step is the actual call, which is described below.
Call argument objects fall into two categories:
- simple objects (xsd:simpleTypes, which can be directly mapped to Smalltalk objects)
- complex objects (in the XML schema, these are xsd:complexType elements)
Simple types are mapped to corresponding Smalltalk objects:
- xsd:string - String
- xsd:int - Integer
- xsd:integer - Integer
- xsd:boolean - Boolean
- xsd:date - Date
- xsd:time - Time
- etc.
There are a few xsd:simpleTypes, for which no standard Smalltalk object exists on all machines (xsd:datetime). Depending on the dialect, these are either mapped to existing classes or to classes from the SOAP framework. For example, xsd:datetime is mapped to the Timestamp class in Smalltalk/X but to SOAP__XeXSDDateTime in VSE.
Complex types must be represented as instances of one of:
- an XeQstruct - this is a special kind of Dictionary, which uses qualified names (e.g. XML names with namespace and prefix) as keys. This is the default representation used by the SOAP framework.
- a Dictionary - if values are given as dictionary instance, it must contain key-value mappings for the sub-elements. The keys are the localName of the schema's element names (i.e. if the element is named foo:someType, then the key should be 'foo' only).
- a special data holder object - this must understand getter- and setter messages, corresponding to the local names of the elements.
A concrete example is found in the BLZ-testcase:
call := client send: 'getBank' withArguments:getBankArg.
Performing the Service Call[Bearbeiten]
Finally, execute the call by giving it a #value message (think of the call object as a block, which does the call for you):
result := call value.
this harmless looking message performs the transfer. It sets up the communication (usually HTTP, where the SOAP message is wrapped into an HTTP-request), sends the message in an asynchronous send process, awaits the response, decodes the returned XML and instantiates the result object(s).
Notice that SOAP allows for multiple return values to be specified in the WSDL.
If this is the case, the result returned by #value is the first return value,
and a sequenceable collection of all return values is retrieved via "call outParameters
".
Debugging / Logging HTTP Traffic[Bearbeiten]
Take a look at "UserPreferences" and the SOAP__SptHTTPRequest's Verbose
class variable. See the chapter at the end on logging flags.
A call object's in/out XML can be inspected (after the call) with:
call in xml inspect. call out xml inspect.
WSDL Class Generator[Bearbeiten]
The wsdl class generator takes WSDL definitions and generates a Smalltalk client class. This contains entries for the operations and type classes which hold the arguments and return values.
Generating Classes[Bearbeiten]
First, instantiate the WSDLClassBuilder.
You need either a service instance (see above) or a WSDL definitions object, as argument to the instantiation message.
For example:
service := SprayWSDLService onXmlString:'... a lot of WSDL here ...' builder := WSDLClassBuilder service: service
or:
definitions := WSDLDefinitions onURL:'http://.....?wsdl'. builder := WSDLClassBuilder definitions: definitions
Then let it generate the classes with:
builder generate
Unless overwritten by the setup messages below, the generated client class will have a name constructed from the service name in the WSDL. It will have a "SOAP__" prefix, followed by the service name and "Client" (i.e. "SOAP__<service name>Client"). For example, if the DeBeKa partnerservice-WSDL is used, the client class name would be "SOAP__PartnerServiceClient".
Data objects are generated for all non-simpleType objects, which are recursively reached/used by the WSDL operations, starting with the individual operation arguments. Datatype class names are constructed in a similar fashion: a "SOAP__" prefix, the service name, an underline, and the type name from the XSD-schema. For example, if the WSDL contains an XSD type named "bestaetigePartner", the generated type class would be named "SOAP__PartnerService_bestaetigePartner". The service name is included, to avoid a name conflict with type classes from other services.
The builder's naming defaults can be overwritten via setup methods described below. This must of course be done before calling the #generate method.
clientClassName: aString[Bearbeiten]
defines (overwrites) the name of the generated client class. Thus, if you say:
builder clientClassName: 'DBK_PService'.
the generated client class will be named "DBK_PService" instead of "SOAP_PartnerServiceClient"
typeClassNamePrefix: aString[Bearbeiten]
defines (overwrites) the prefix to be used for type classes. This includes the namespace prefix. Thus, if you say:
builder typeClassNamePrefix: 'DBK_'.
all generated type classes will be named "DBK_<schema name>".
classNamePrefix: aString[Bearbeiten]
defines (overwrites) the prefix to be used for all classes (i.e. type- and client classes). This includes the namespace prefix. Thus, if you say:
builder classNamePrefix: 'DBK_'.
all generated classes will be named "DBK_<schema name>", and the client class will be "DBK_<serviceName>Client".
Generated Data Holder Classes (TypeClasses)[Bearbeiten]
For each used XSD type, a corresponding Smalltalk class is created. These are (and should be) complete passive objects which exist only as data holders. Their instance variable structure and getter/setter interface directly corresponds to the element-structure of the corresponding XSD types.
However, all element names are converted to lowercaseFirst (unless the forceLowercaseFieldNames option is set to false, as described below). Also, multiple occurs fields are given a name with a trailing underscore ("_"), and an additional adder-method is generated. I.e. for a multiple-occurs element named "foo", a getter named "foo_" (which returns the multiple elements as an array), a setter named "foo_:", which expects the multiple values as an array), and an adder "add_foo:" (which expects a single object to be added) are generated.
The translation to lowercase is done, because most Smalltalks give a warning about or even forbid instance variables with an uppercase first character. However, you may prefer having the original XSD schema names as instance variables and getter/setter names. Then use:
builder forceLowercaseFieldNames: false
before calling the generate method.
Using Generated Client Classes[Bearbeiten]
The client class is generated as a subclass of SOAP__SprayWSDLService. It has to be instantiated, and contains a number of helper methods and the SOAP operation API in a category named "operations". Each operation expects a request-argument object as parameter, as specified in the WSDL. For example, in the partnerService, you will find methods named "bestaetigePartner:", "listPartner:" etc.
The generated methods are written to include comments and naming hints (argument names) which will help in instantiating the required argument objects correctly.
The default destination URL is returned by the "destinationUrl" method. By default, this returns the URL as found in the original WSDL. A setter method "destinationUrl:" allows for this default to be overwritten.
To instantiate a service client, use:
serviceClient := <generatedServiceClientClass> new.
to use a different URL, change it with:
serviceClient destinationUrl: 'http://...'.
Once instantiated, the serviceClient can be reused for multiple requests to the same service host. It does not keep connections open between service calls (unless your underlying HTTP interface does so, and the HTTP connection was opened with the "connection: keep" option - which is not done by default).
Then setup the service arguments. Look into the source of the client classes' operation methods, to see what it expects, and the source of the corresponding type-class to see what elements it has:
arg := <operationTypeClass> new. arg foo: valueForFoo. arg bar: valueForBar. ...
Finally, perform the service call with:
rslt := serviceClient operation: arg.
the returned result-object will be an instance of the response type-class, with getters as specified in the WSDL. Look at the comments in the getters, to see what you get.
Instantiating Argument Objects[Bearbeiten]
The generated data holding type classes will have getters and setters for their elements. If the schema includes restrictions (such as a length restriction on a string, or an enumeration of allowed values), the setters will also perform verification of the argument. In addition, the generated code includes comments, which describe any restrictions.
The verification can be disabled via a central flag, as returned by: "SOAP__SoapSetting verifySoapCallArguments". You can change this method to return false for deployed images.
Accessing the Last Call Object (after a service call)[Bearbeiten]
The generated client class has an instance variable, which holds the previous request object. This can be inspected in case of an error. For example,
serviceClient lastRequest out xml
returns the last out-going request's full XML envelope,
and:
serviceClient lastRequest in xml
contains the last response.
Example: PartnerService[Bearbeiten]
Generating the PartnerService classes[Bearbeiten]
The following code example is also found in "WSDLClassBuilderTest >> testClassBuilder_02".
Generating the partnerService classes:
definitions := WSDLDefinitions onUrl: 'http://plan-eval-esb.services.debeka.de:30050/partner/proxyservice/PartnerBusinessService?WSDL'. builder := WSDLClassBuilder definitions:definitions. builder generate.
this will generate the following classes (into the class category "SOAP-Generated-partnerService"):
- SOAP__PartnerServiceClient
- SOAP__PartnerService_addressType
- SOAP__PartnerService_bestaetigepartner
- SOAP__PartnerService_fehler
- ...
- SOAP__PartnerService_personType
- SOAP__PartnerService_seitenSuche
Using PartnerService[Bearbeiten]
To call the partnerService at its default service port address, use:
service := SOAP__PartnerService new. suchArg := SOAP_PartnerService_seitenSuche new. suchArg ergebnisType:'ALLES'. result := service listePartner: suchArg. "result will be an instance of kontrollTyp" Transcript show: result code. Transcript show: result fehler. Transcript show: result information.
for a different service URL (in-house test service), use:
service := SOAP__PartnerService new. service destinationUrl:'http://testServer:8080/partner/proxyservice/PartnerBusinessService. ...
SOAP Package Structure[Bearbeiten]
The whole package is structured into a number of sub-packages, of which some are usable outside the SOAP context. Each package has a subpackage (named "XX_test" in ST/X and usually "XXTES" in VSE).
In ST/X, all SOAP classes live in a separate namespace ("SOAP"), and thus have a "SOAP::" prefix in their name. Inside the namespace, the prefix is not needed to refer to a class which is in the same namespace. Therefore, in ST/X code, you will find class references without prefix.
For VSE, which has no namespaces, class names are translated by prefixing the name with "<namespace>__". Thus all classes will have a "SOAP__" prefix in VSE. This is automatically done by the STX-VSE class exporter, which was developed specifically for this purpose.
Notice, that this automatic rewrite can of course not detect situations where class names are constructed dynamically, and referred to via "Smalltalk at:". Such code had to be identified and manually changed to deal with both dialects. Usually, you will find code like "Smalltalk isSmalltalkX ifTrue:[...]" or "Smalltalk isVisualSmalltalkEnterprise ifTrue:[...]" in those parts. The goal is to keep a common code base, so that future improvements and fixes will be immediately available in both dialects.
Package: xe ("stx_goodies_soap_xe")[Bearbeiten]
contains classes to represent qualified names, XSD schema definitions, XSD typespaces and mappings etc. there are no input/output or GUI related operations in this package. Most of the classes found there can be mapped one-by-one to corresponding elements from an XSD schema definition. Object hierarchies of them are created by the WSDL readers, the SOAP envelope creators and the SOAP encoders.
Package: spray ("stx_goodies_soap_spray")[Bearbeiten]
the basic SOAP framework. Includes both client and server code (however, the server part was not part of the VSE porting effort, and may not run without further debugging). The spray package does not need a WSDL. In theory, it is possible to setup the above described service/client/call objects manually, by appropriate instance creation and initialization messages. In practice, the setup is too complicated, and the WSDL subpackage does this for you.
Package: wsdl ("stx_goodies_soap_wsdl")[Bearbeiten]
this contains classes to represent the information inside a WSDL, and to create a service object from it. Most of the code is in WSDLDefinitions, which deals with all xsd-schema handling, especially with the import of other documents (using transport objects).
Package: yaxo ("stx_goodies_xml_yaxo")[Bearbeiten]
the YAXO ("yet another xml o-stands-for-whatever") XML parser. Reads XML and generates a hierarchy of XML nodes. This parser lives in its own namespace called "YAXO"; thus, in VSE, all of its classes will have a "YAXO__" prefix.
The SOAP framework is prepared to use different XML parsers, and uses adaptors which transforms the different XML nodes into its own XeNode representation. Currently three adaptors are present, for YAXO, VisualWorks XML parser and ExoboxXML parser. XML parsing speed is crucial when WSDLs are read and to decode SOAP envelopes. If performance ever becomes a major issue, you may replace this by a high speed XML parser (written in C), and create a matching adaptor (take SOAP::SoYaxXMLParserAdapter as a base).
Package: soap ("stx_goodies_soap")[Bearbeiten]
contains all the rest, such as error classes, abstract classes, encoders/decoders, transport interfaces (adapters to HTTP), etc.
Package: auth ("stx_goodies_soap_spray_auth")[Bearbeiten]
additional classes which deal with authentication. This is currently not maintained, and provided "as-is". No effort has been made in porting or verifying its usefulness. However, it seems to compile (at least) in VSE. Exept does not currently use these classes in any of its projects - we recommend using regular SOAP over a secure SSH (HTTPS) connection. Then this package will not be needed at all.
Classes of Special Interest[Bearbeiten]
SOAP__SptHTTPRequest[Bearbeiten]
performs the actual HTTP get operation for both WSDL fetching AND the actual SOAP call. The entry is "send:" which dispatches to either "send_VSE" or "send_NonVSE" (for ST/X). There, different mechanisms are used. "send_VSE" calls out to the WinInet DLL, after setting up the parameters, whereas "send_NonVSE" uses the Smalltalk socket interface and/or HTTP framework from Smalltalk/X. In ST/X, the call is asynchronous and non-blocking, meaning that the GUI is still responding while waiting for a response. In VSE, the DLL-call is blocking the VM. Be careful in case of bad/wrong network setup, if a service is unreachable, the GUI may be blocked for a longer time (it is probably a good idea to make the timeouts configurable...)
SOAP__SpraySoapSend[Bearbeiten]
the basic call entry. By placing a halt on the "value" method, you can single step through the send/receive process. In case of decoding errors (wrong XML/WSDL), it provides the raw XML response via additional getter methods (try #inXmlOrEmpty). Also, in case of xsd:schema errors (wrong restrictions etc.), less strict API entries (extractResuming) are provided which may be able to decode a response even if erroneous.
SOAP__WSDLClassBuilder[Bearbeiten]
the new builder class, which was not part of the original SOAP package. Generates the client class, enumerates all reachable non-simple type classes and uses the XeClassBuilder to generate type classes. XeClassBuilder used to be present in the original (2010 SOAP) version, but has been much enhanced to generate reasonable argument and type names, comments and verification code. Also, array handling and restricted simple type handling was added.
UserPreferences[Bearbeiten]
In ST/X, the "UserPreferences current" holds all settings of the current user session. In VSE, a mimicri interface is provided for compatibility. This provides some debugging/logging flags of interest:
- logHTTPRequests
to enable/disable logging of HTTP traffic on the Transcript. Set to true during development, false for deployed applications. The SOAP__SptHTTPRequest also contains a private Verbose class variable, which can be also set to true (nil counts as false).
- soapLoggingLevel
controls how much debugging info is emitted from SOAP itself.
- soapErrorDebugging
if this returns true, you will get a debugger whenever a SOAP-internal error occurs. Otherwise, the error might be reported as a SOAPFault.