10 The gSOAP Service Operation Specification Format
A service operation is specified as a C/C++ function prototype in a header file. The function is REQUIRED to return int, which is used to represent a SOAP error code, see Section 10.2. Multiple service operations MAY be declared together in one header file. The general format of a service operation specification is:
[int] [namespace_prefix__]method_name([inparam1, inparam2, ...,] outparam); |
where
- namespace_prefix__
- is the optional namespace prefix of the method (see identifier translation rules 10.3)
- method_name
- it the service operation name (see identifier translation rules 10.3)
- inparam
- is the declaration of an input parameter of the service operation
- outparam
- is the declaration of the output parameter of the service operation
This simple form can only pass a single, non-struct and non-class
type output parameter. See 10.1 for passing multiple output
parameters. The name of the declared function namespace_prefix__
method_name must be unique and cannot match the name of a struct,
class, or enum declared in the same header file.
The method request is encoded in SOAP as an XML element and the
namespace prefix, method name, and input parameters are encoded using
the format:
<[namespace-prefix:]method_name xsi:type="[namespace-prefix:]method_name> <inparam-name1 xsi:type="...">...</inparam-name1> <inparam-name2 xsi:type="...">...</inparam-name2> ... </[namespace-prefix:]method_name> |
where the inparam-name accessors are the element-name representations of the inparam parameter name declarations, see
Section 10.3. (The optional parts are shown enclosed in [].)
The XML response by the Web service is of the form:
<[namespace-prefix:]method-nameResponse xsi:type="[namespace-prefix:]method-nameResponse> <outparam-name xsi:type="...">...</outparam-name> </[namespace-prefix:]method-nameResponse> |
where the outparam-name accessor is the element-name representation of the outparam parameter name declaration, see
Section 10.3. By convention, the response element name is the method name ending in Response.
See 10.1 on how to change the declaration if the service response element name is different.
The gSOAP soapcpp2 tool generates a stub routine for the service
operation. This stub is of the form:
int soap_call_[namespace_prefix__]method_name(struct soap *soap, char *URL, char *action, [inparam1, inparam2, ...,] outparam); |
This proxy can be called by a client application to perform the service operation
call.
The gSOAP soapcpp2 tool generates a skeleton routine for the
service operation. The skeleton function is:
int soap_serve_[namespace_prefix__]method_name(struct soap *soap); |
The skeleton routine, when called by a service application, will attempt to
serve a request on the standard input. If no request is present or if the
request does not match the method name, SOAP_NO_METHOD is returned.
The skeleton routines are automatically called by the generated
soap_serve routine that handles all requests.
10.1 Service Operation Parameter Passing
The input parameters of a service operation MUST be passed by value. Input
parameters cannot be passed by reference with the & reference operator,
but an input parameter value MAY be passed by a pointer to the data. Of
course, passing a pointer to the data is preferred when the size of the data of
the parameter is large. Also, to pass instances of (derived) classes, pointers
to the instance need to be used to avoid passing the instance by value which
requires a temporary and prohibits passing derived class instances. When two
input parameter values are identical, passing them using a pointer has the
advantage that the value will be encoded only once as multi-reference (hence,
the parameters are aliases). When input parameters are passed using a pointer,
the data pointed to will not be modified by the service operation and returned to
the caller.
The output parameter MUST be passed by reference using & or by using a
pointer. Arrays are passed by reference by default and do not require the use
of the reference operator &.
The input and output parameter types have certain limitations, see
Section 9.10
If the output parameter is a struct or class type, it is
considered a service operation response element instead of a simple output
parameter value. That is, the name of the struct or class is the
name of the response element and the struct or class fields are
the output parameters of the service operation, see also 7.1.7. Hence,
if the output parameter has to be a struct or class, a response
struct or class MUST be declared as well. In addition, if a
service operation returns multiple output parameters, a response struct or
class MUST be declared. By convention, the response element is the
service operation name ending with "Response".
The general form of a response element declaration is:
struct [namespace_prefix__]response_element_name { outparam1; outparam2; ... }; |
where
- namespace_prefix__
- is the optional namespace prefix of the response element (see identifier translation rules 10.3)
- response_element_name
- it the name of the response element (see identifier translation rules 10.3)
- outparam
- is the declaration of an output parameter of the service operation
The general form of a service operation specification with a response element declaration for (multiple) output parameters is:
[int] [namespace_prefix__]method_name([inparam1, inparam2, ...,] struct [namespace_prefix__]response_element_name {outparam1[, outparam2, ...]} &anyparam); |
The choice of name for anyparam has no effect on the SOAP encoding and decoding and is only used as a place holder for the
response.
The method request is encoded in SOAP as an independent element and the
namespace prefix, method name, and input parameters are encoded using the
format:
<[namespace-prefix:]method-name xsi:type="[namespace-prefix:]method-name> <inparam-name1 xsi:type="...">...</inparam-name1> <inparam-name2 xsi:type="...">...</inparam-name2> ... </[namespace-prefix:]method-name> |
where the inparam-name accessors are the element-name representations of
the inparam parameter name declarations, see Section 10.3.
(The optional parts resulting from the specification are shown enclosed in
[].)
The method response is expected to be of the form:
<[namespace-prefix:]response-element-name xsi:type="[namespace-prefix:]response-element-name> <outparam-name1 xsi:type="...">...</outparam-name1> <outparam-name2 xsi:type="...">...</outparam-name2> ... </[namespace-prefix:]response-element-name> |
where the outparam-name accessors are the element-name representations of
the outparam parameter name declarations, see Section 10.3.
(The optional parts resulting from the specification are shown enclosed in
[].)
The input and/or output parameters can be made anonymous, which allows the
deserialization of requests/responses with different parameter names as is
endorsed by the SOAP 1.1 specification, see Section 7.1.13.
10.2 Error Codes
The error codes returned by the stub and skeleton routines are listed below.
|
The error codes that are returned by a stub routine (proxy) upon receiving a
SOAP Fault from the server are marked (*). The remaining error codes are
generated by the proxy itself as a result of problems with a SOAP payload. The
error code is SOAP_OK when the service operation call was successful (the
SOAP_OK predefined constant is guaranteed to be 0). The error
code is also stored in soap.error, where soap is a variable that
contains the current runtime context. The function
soap_print_fault(struct soap *soap, FILE *fd) can be called to
display an error message on fd where current value of the
soap.error variable is used by the function to display the error. The
function soap_print_fault_location(struct soap *soap, FILE *fd)
prints the location of the error if the error is a result from parsing XML.
Use soap_sprint_fault(struct soap*, char *buf, size_t len) to print the error to a string.
A service operation implemented in a SOAP service MUST return an error code as the
function's return value. SOAP_OK denotes success and SOAP_FAULT
denotes an exception. The exception details can be assigned with the
soap_receiver_fault(struct soap *soap, const char *faultstring,
const char *detail) which sets the strings
soap.fault->faultstring and
soap.fault->detail for SOAP 1.1, and
soap.fault->SOAP_ENV__Reason and
soap.fault->SOAP_ENV__Detail for SOAP 1.2, where
soap is a variable that contains the current runtime context, see
Section 12.
A receiver error indicates that the service can't handle the request, but can possibly recover from the error.
To return an unrecoverable SOAP 1.1/1.2 error, use soap_sender_fault(struct soap *soap, const char *faultstring, const char *detail).
To return a HTTP error code a service method can simply return the HTTP error code number.
For example, return 404; returns a "404 Not Found" HTTP error back to the client. The soap.error
is set to the HTTP error code at the client side.
The HTTP 1.1 error codes are:
|
The error codes are given for informational purposes only. The HTTP
protocol requires the proper actions after an error is issued. gSOAP's
HTTP 1.0/1.1 handling is automatic.
10.3 C/C++ Identifier Name to XML Tag Name Mapping
One of the "secrets" behind the power and flexibility of gSOAP's encoding and
decoding of service operation names, class names, type identifiers, and struct or
class fields is the ability to specify namespace prefixes with these names that
are used to denote their encoding style. More specifically, a C/C++ identifier
name of the form
[namespace_prefix__]element_name |
where the prefix and the element name are separated by double underscores will be encoded in XML as
<[namespace-prefix:]element-name ...> |
The underscore pair (__) separates the namespace prefix from the
element name. Each namespace prefix has a namespace URI specified by a
namespace mapping table 10.4, see also
Section 7.1.2. The namespace URI is a unique identification that
can be associated with the service operations and data types. The namespace URI
disambiguates potentially identical service operation names and data type names
used by disparate organizations.
XML element names are NCNames (restricted strings) that MAY contain
hyphens, dots, and underscores. The special characters in the XML
element names of service operations, structs, classes, typedefs, and fields can be
controlled using the following conventions: A single underscore in a
namespace prefix or identifier name is replaced by a hyphen (-) in the
XML element name. For example, the identifier name SOAP_ENC__ur_type
is represented in XML as SOAP-ENC:ur-type. The sequence _DOT is
replaced by a dot (.), and the sequence _USCORE is replaced by
an underscore (_) in the corresponding XML element name. For example:
class n_s__biz_DOTcom { char *n_s__biz_USCOREname; }; |
is encoded in XML as:
<n-s:biz.com xsi:type="n-s:biz.com"> <n-s:biz_name xsi:type="string">Bizybiz</n-s:biz_name> </n-s:biz.com> |
Trailing underscores of an identifier name are not translated into the XML
representation. This is useful when an identifier name clashes with a C++
keyword. For example, return is often used as an accessor name in a SOAP
response element. The return element can be specified as return_
in the C++ source code. Note that XML should be treated as case sensitive, so
the use of e.g. Return may not always work to avoid a name clash with the
return keyword. The use of trailing underscores also allows for
defining structs and classes with essentially the same XML Schema
type name, but that have to be distinguished as seperate C/C++ types.
For decoding, the underscores in identifier names act as wildcards. An XML
element is parsed and matches the name of an identifier if the name is
identical to the element name (case insensitive) and the underscores in the
identifier name are allowed to match any character in the element name. For
example, the identifier name I_want__soap_fun_the_bea___DOTcom
matches the element name I-want:SOAP4fun@the-beach.com.
By default, soapcpp2 generates data bindings in which all XML elements are and attributes are unqualified:
//gsoap x schema namespace: urn:x struct x__record { @char * type; char * name; }; |
where the name element and the type attribute are unqualified in the XML content (for example to facilitate SOAP RPC encoding).
The rules for SOAP services that are document style are different:
//gsoap x schema namespace: urn:x //gsoap x service style: document struct x__record { @char * type; char * name; }; |
where x is associated with a service. For document style all elements are qualified and attributes are unqualified.
To force qualification of elements and attributes, use the "form" directive:
//gsoap x schema namespace: urn:x //gsoap x schema form: qualified struct x__record { @char * type; char * name; }; |
You can also use "elementForm" and "attributeForm" directives to (un)qualify element and attributes of the schema, respectively.
Because the soapcpp2-generated serializers follow the
qualified/unqualified forms of the schemas, there is normally no need to
explicitly qualify struct/class members because automatic encoding rules will
be used.
If explicit qualification is needed, this can be done using the prefix convention:
//gsoap x schema namespace: urn:x //gsoap y schema namespace: urn:y struct x__record { @char * xsi__type; char * y__name; }; |
which ensures that there cannot be any name clashes between members of
the same name defined in different schemas (consider for example name and y__name), but this can clutter the representation when clashes do not occur.
An alternative to the prefix convention is the use of "colon notation" in the
gSOAP header file. This deviation from the C/C++ syntax allows you to bind
type names and struct and class members to qualified and unqualified XML tag names explicitly,
thus bypassing the default mechanism that automatically qualifies or
unqualifies element and attribute tag names based on the schema
element/attribute form.
The colon notation for type names, struct/class names and members overrides the prefix qualification rules explicitly:
//gsoap x schema namespace: urn:x //gsoap y schema namespace: urn:y struct x:record { @char * xsi:type; char * y:name; }; |
where x and y are namespace prefixes that MUST be declared with a directive. The xsi:type member is an XML attribute in the xsi namespace.
The soapcpp2 tool maps this to the following struct without the annotations:
// This code is generated from the above by soapcpp2 in soapStub.h: struct record { char *type; /* optional attribute of type xsd:string */ char *name; /* optional element of type xsd:string */ }; |
The soapcpp2 tool also generates XML schemas with element and attribute
references. That is, y:name is referenced from the y schema by the
x:record complexType defined in the x schema.
The colon notation also allows you to override the element/attribute form to unqualified for qualified schemas:
//gsoap x schema namespace: urn:x //gsoap x schema form: qualified struct x:record { @char * :type; char * :name; }; |
where the colon notation ensures that both type and name are
unqualified in the XML content, which overrides the default qualified forms of
the x schema.
Note that the use of colon notation to bind namespace prefixes to type names
(typedef, enum, struct, and class names) translates to code without the
prefixes. This means that name clashes can occur between types with identical unquaified names:
enum x:color { RED, WHITE, BLUE }; enum y:color { YELLOW, ORANGE }; // illegal enum name: name clash with x:color |
while prefixing with double underscores never lead to clashes:
enum x__color { RED, WHITE, BLUE }; enum y__color { YELLOW, ORANGE }; // no name clash |
Also note that colon notation has a very different role than the C++ scope
operator ::. The scope operator cannot be used in places where we need
colon notation, such as struct/class member fields.
The default mechanism that associates XML tag names with the names of struct
and class member fields can be overriden by "retagging" names with the
annotation of `tag` placed next to the member field name.
This is particularly useful to support legacy code for which the fixed
naming of member fields cannot be easily changed. For example:
//gsoap x schema namespace: urn:x //gsoap x schema form: qualified struct x:record { @char * t`type`; char * s`full-name`; }; |
This maps the t member to the x:type XML attribute tag and s
member to the x:full-name XML element tag. Note that both tags are namespace
qualified as per schema declaration.
10.4 Namespace Mapping Table
A namespace mapping table MUST be defined by clients and service applications.
The mapping table is used by the serializers and deserializers of the stub and
skeleton routines to produce a valid SOAP payload and to validate an incoming
SOAP payload. A typical mapping table is shown below:
struct Namespace namespaces[] = { // {"ns-prefix", "ns-name"} {"SOAP-ENV", "http://schemas.xmlsoap.org/soap/envelope/"}, // MUST be first {"SOAP-ENC", "http://schemas.xmlsoap.org/soap/encoding/"}, // MUST be second {"xsi", "http://www.w3.org/2001/XMLSchema-instance"}, // MUST be third {"xsd", "http://www.w3.org/2001/XMLSchema"}, // Required for XML Schema types {"ns1", "urn:my-service-URI"}, // The namespace URI of the service operations {NULL, NULL} // end of table }; |
Each namespace prefix used by a identifier name in the header file
specification (see Section 10.3) MUST have a binding to a
namespace URI in the mapping table. The end of the namespace mapping table MUST
be indicated by the NULL pair. The namespace URI matching is case
insensitive. A namespace prefix is distinguished by the occurrence of a pair
of underscores (__) in an identifier.
An optional namespace pattern MAY be provided with each namespace mapping table
entry. The patterns provide an alternative namespace matching for the
validation of decoded SOAP messages. In this pattern, dashes (-) are
single-character wildcards and asterisks (*) are multi-character
wildcards. For example, to decode different versions of XML Schema type with
different authoring dates, four dashes can be used in place of the specific
dates in the namespace mapping table pattern:
struct Namespace namespaces[] = { // {"ns-prefix", "ns-name", "ns-name validation pattern"} ... {"xsi", "http://www.w3.org/2001/XMLSchema-instance", "http://www.w3.org/----/XMLSchema-instance"}, {"xsd", "http://www.w3.org/2001/XMLSchema", "http://www.w3.org/----/XMLSchema"}, ... |
Or alternatively, asterisks can be used as wildcards for multiple characters:
struct Namespace namespaces[] = { // {"ns-prefix", "ns-name", "ns-name validation pattern"} ... {"xsi", "http://www.w3.org/2001/XMLSchema-instance", "http://www.w3.org/*/XMLSchema-instance"}, {"xsd", "http://www.w3.org/2001/XMLSchema", "http://www.w3.org/*/XMLSchema"}, ... |
A namespace mapping table is automatically generated together with a WSDL file
for each namespace prefix that is used for a service operation specified in the header file.
This namespace mapping table has entries for all namespace prefixes. The
namespace URIs need to be filled in. These appear as http://tempuri.org
in the table. See Section 19.2 on how to specify the namespace
URIs in the header file.
For decoding elements with namespace prefixes, the namespace URI associated with the namespace prefix (through the xmlns
attribute of an XML element) is searched from the
beginning to the end in a namespace mapping table,
and for every row the following tests are performed as part of the validation process:
- the string in the second column matches the namespace URI (case insensitive)
- the string in the optional third column matches the namespace URI (case insensitive), where - is a one-character wildcard and * is a multi-character wildcard
When a match is found, the namespace prefix in the first column of the
table is considered semantically identical to the namespace prefix used
by the XML element to be decoded, though the prefix names may differ.
A service will respond with the namespace that it received from a client
in case it matches a pattern in the third column.
For example, let's say we have the following structs:
struct a__elt { ... }; struct b__elt { ... }; struct k__elt { ... }; |
and a namespace mapping table in the program:
struct Namespace namespaces[] = { // {"ns-prefix", "ns-name", "ns-name validation pattern"} ... {"a", "some uri"}, {"b", "other uri"}, {"c", "his uri", "* uri"}, ... |
Then, the following XML elements will match the structs:
<n:elt xmlns:n="some URI"> matches the struct name a__elt ... <m:elt xmlns:m="other URI"> matches the struct name b__elt ... <k:elt xmlns:k="my URI"> matches the struct name c__elt ... |
The response of a service to a client request that uses the namespaces listed above,
will include my URI for the name space of element k.
It is possible to use a number of different namespace tables and select
the one that is appropriate.
For example, an application might contact many different Web services
all using different namespace URIs.
If all the URIs are stored in one table, each service operation
invocation will dump the whole namespace
table in the SOAP payload. There is no technical problem with that, but
it can be ugly when the table is large.
To use different namespace tables, declare a pointer to a table and set
the pointer to a particular table before service operation
invocation. For example:
struct Namespace namespacesTable1[] = { ... }; struct Namespace namespacesTable2[] = { ... }; struct Namespace namespacesTable3[] = { ... }; struct Namespace *namespaces; ... struct soap soap; ... soap_init(&soap); soap_set_namespaces(&soap, namespaceTable1); soap_call_remote_method(&soap, URL, Action, ...); ... |