14 MIME Attachments
The gSOAP toolkit supports MIME attachments as per SOAP with Attachments (SwA) specification (http://www.w3.org/TR/SOAP-attachments). In the following discussion, MIME attachment data is assumed to be resident in memory for sending operations and MIME attachments received will be stored in memory. MTOM and DIME attachments on the other hand can be streamed and therefore MTOM/DIME attachment data does not need to be stored in memory, see Section 15 and 16. Transmitting multipart/related MIME attachments with a SOAP/XML message is accomplished with two functions, soap_set_mime and soap_set_mime_attachment. The first function is for initialization purposes and the latter function is used to specify meta data and content data for each attachment.
14.1 Sending a Collection of MIME Attachments (SwA)
The following functions should be used to set up a collection of multipart/related MIME attachments for transmission with a SOAP/XML message.
|
When providing a MIME boundary with soap_set_mime, you have to make
sure the boundary cannot match any SOAP/XML message content.
Or you can simply pass NULL and let gSOAP select a safe boundary for you.
The internal list of attachments is destroyed with soap_end, you should
call this function sometime after the message exchange was completed (the
content of the block of memory referred to by the ptr parameter is
unaffected).
The following example shows how a multipart/related HTTP message with three
MIME attachments is set up and transmitted to a server. The first attachment
contains the SOAP message. The second and third attachments contain image data.
In this example we let the SOAP message body refer to the attachments using
href attributes. The struct claim__form data type includes a
definition of a href attribute for this purpose.
struct claim__form form1, form2; form1.href = "cid:claim061400a.tiff@claiming-it.com"; form2.href = "cid:claim061400a.jpeg@claiming-it.com"; /* initialize and enable MIME */ soap_set_mime(soap, "MIME_boundary", "<claim061400a.xml@claiming-it.com>"); /* add a base64 encoded tiff image (tiffImage points to base64 data) */ soap_set_mime_attachment(soap, tiffImage, tiffLen, SOAP_MIME_BASE64, "image/tiff", "<claim061400a.tiff@claiming-it.com>", NULL, NULL); /* add a raw binary jpeg image (jpegImage points to raw data) */ soap_set_mime_attachment(soap, jpegImage, jpegLen, SOAP_MIME_BINARY, "image/jpeg", "<claim061400a.jpeg@claiming-it.com>", NULL, NULL); /* send the forms as MIME attachments with this invocation */ if (soap_call_claim__insurance_claim_auto(soap, form1, form2, ...)) // an error occurred else // process response |
Note: the above example assumes that the boundary MIME_boundary does not match any part of the SOAP/XML message.
The claim__form struct is declared in the gSOAP header file as:
struct claim__form { @char *href; }; |
This data type defines the parameter data of the operation. The claim forms in
the SOAP/XML message consist of hrefs to the claim forms attached. The
produced message is similar to the last example shown in the SOAP with
Attachments specification (http://www.w3.org/TR/SOAP-attachments). Note that
the use of href or other attributes for referring to the MIME attachments
is optional according to the SwA standard.
To associate MIME attachments with the request and response of a service operation in the generated WSDL, please see Section 16.1.
The server-side code to transmit MIME attachments back to a client is similar:
int claim__insurance_claim_auto(struct soap *soap, ...) { soap_set_mime(soap, NULL, NULL); // enable MIME // add a HTML document (htmlDoc points to data, where the HTML doc is stored in compliance with 7bit encoding RFC2045) if (soap_set_mime_attachment(soap, htmlDoc, strlen(htmlDoc), SOAP_MIME_7BIT, "text/html", "<claim061400a.html@claiming-it.com>", NULL, NULL)) { soap_clr_mime(soap); // don't want fault with attachments return soap->error; } return SOAP_OK; } |
It is also possible to attach data to a SOAP fault message.
Caution: DIME in MIME is supported. However, gSOAP will not verify whether
the MIME boundary is present in the DIME attachments and therefore will not
select a boundary that is guaranteed to be unique. Therefore, you must provide
a MIME boundary with soap_set_mime that is unique when using DIME in
MIME.
14.2 Retrieving a Collection of MIME Attachments (SwA)
MIME attachments are automatically parsed and stored in memory.
After receiving a set of MIME attachments, either at the client-side or
the server-side, the list of MIME attachments can be traversed to extract
meta data and the attachment content. The first attachment in the collection of
MIME attachments always contains meta data about the SOAP message
itself (because the SOAP message was processed the attachment does not contain
any useful data).
To traverse the list of MIME attachments in C, you use a loop similar to:
struct soap_multipart *attachment; for (attachment = soap.mime.list; attachment; attachment = attachment->next) { printf("MIME attachment:\n"); printf("Memory=%p\n", (*attachment).ptr); printf("Size=%ul\n", (*attachment).size); printf("Encoding=%d\n", (int)(*attachment).encoding); printf("Type=%s\n", (*attachment).type?(*attachment).type:"null"); printf("ID=%s\n", (*attachment).id?(*attachment).id:"null"); printf("Location=%s\n", (*attachment).location?(*attachment).location:"null"); printf("Description=%s\n", (*attachment).description?(*attachment).description:"null"); } |
C++ programmers can use an iterator instead, as in:
for (soap_multipart::iterator attachment = soap.mime.begin(); attachment != soap.mime.end(); ++attachment) { cout << "MIME attachment:" << endl; cout << "Memory=" << (void*)(*attachment).ptr << endl; cout << "Size=" << (*attachment).size << endl; cout << Ëncoding=" << (*attachment).encoding << endl; cout << "Type=" << ((*attachment).type?(*attachment).type:"null") << endl; cout << "ID=" << ((*attachment).id?(*attachment).id:"null") << endl; cout << "Location=" << ((*attachment).location?(*attachment).location:"null") << endl; cout << "Description=" << ((*attachment).description?(*attachment).description:"null") << endl; } |
Note: keep in mind that the first attachment is associated with the SOAP
message and you may want to ignore it.
A call to soap_end removes all of the received MIME data. To preserve an
attachment in memory, use soap_unlink on the ptr field of the
soap_multipart struct. Recall that the soap_unlink function is
commonly used to prevent deallocation of deserialized data.
15 DIME Attachments
The gSOAP toolkit supports DIME attachments as per DIME specification, see http://msdn.microsoft.com/library/en-us/dnglobspec/html/draft-nielsen-dime-02.txt Applications developed with gSOAP can transmit binary DIME attachments with or without streaming messages. Without streaming, all data is stored and retrieved in memory, which can be prohibitive when transmitting large files on small devices. In contrast, with DIME streaming, data handlers are used to pass the data to and from a resource, such as a file or device. With DIME output streaming, raw binary data is send from a data source in chunks on the fly without buffering the entire content to save memory. With DIME input streaming, raw binary data will be passed to data handlers (callbacks).
15.1 Sending a Collection of DIME Attachments
The following functions can be used to explicitly set up a collection of
DIME attachments for transmission with a SOAP/XML message body.
The attachments can be streamed, as described in
Section 15.4. Without streaming, each attachment must refer
to a block of data in memory.
|
These functions allow DIME attachments to be added without requiring message
body references. This is also referred to as the open DIME attachment style.
The closed attachment style requires all DIME attachments to be referenced from
the SOAP message body with href (or similar) references. For the closed
style, gSOAP supports an automatic binary data serialization method, see
Section 15.3.
15.2 Retrieving a Collection of DIME Attachments
DIME attachments are automatically parsed and stored in memory (or passed to
the streaming handlers, when applicable). After receiving a set of DIME
attachments, either at the client-side or the server-side, the list of DIME
attachments can be traversed to extract meta data and the attachment content.
To traverse the list of DIME attachments in C, you use a loop similar to:
struct soap_multipart *attachment; for (attachment = soap.dime.list; attachment; attachment = attachment->next) { printf("DIME attachment:\n"); printf("Memory=%p\n", (*attachment).ptr); printf("Size=%ul\n", (*attachment).size); printf("Type=%s\n", (*attachment).type?(*attachment).type:"null"); printf("ID=%s\n", (*attachment).id?(*attachment).id:"null"); } |
C++ programmers can use an iterator instead, as in:
for (soap_multipart::iterator attachment = soap.dime.begin(); attachment != soap.dime.end(); ++attachment) { cout << "DIME attachment:" << endl; cout << "Memory=" << (void*)(*attachment).ptr << endl; cout << "Size=" << (*attachment).size << endl; cout << "Type=" << ((*attachment).type?(*attachment).type:"null") << endl; cout << "ID=" << ((*attachment).id?(*attachment).id:"null") << endl; } |
The options field is available as well. The options content is
formatted according to the DIME specification: the first two bytes are reserved
for the option type, the next two bytes store the size of the option data,
followed by the (binary) option data.
A call to soap_end removes all of the received DIME data. To preserve an
attachment in memory, use soap_unlink on the ptr field of the
soap_multipart struct. Recall that the soap_unlink function is
commonly used to prevent deallocation of deserialized data.
15.3 Serializing Binary Data in DIME
Binary data stored in extended xsd:base64Binary and xsd:hexBinary
types can be serialized and deserialized as DIME attachments. These attachments
will be transmitted prior to the sequence of secondary DIME attachments defined
by the user with soap_set_dime_attachment as explained in the
previous section. The serialization process is automated and DIME attachments
will be send even when soap_set_dime or
soap_set_dime_attachment are not used.
The xsd:base64Binary XSD type is defined in gSOAP as a struct or class by
struct xsd__base64Binary { unsigned char *__ptr; // pointer to raw binary data int __size; // size of the block of data }; |
To enable serialization of the data in DIME, we extend this type with three
additional fields:
struct xsd__base64Binary { unsigned char *__ptr; int __size; char *id; char *type; char *options; }; |
The three additional fields consist of an id field for attachment
referencing (typically a content id (CID) or UUID), a type field to
specify the MIME type of the binary data, and an options field to
piggy-back additional information with a DIME attachment. The order of the
declaration of the fields is significant. In addition, no other fields or
methods may be declared before any of these fields in the struct/class, but
additional fields and methods may appear after the field declarations. An
extended xsd__hexBinary declaration is similar.
The id and type fields contain text. The set the DIME-specific
options field, you can use the soap_dime_option function:
char *soap_dime_option(struct soap *soap, unsigned short type, const char *option) |
returns a string with this encoding. For example
struct xsd__base64Binary image; image.__ptr = ...; image.__size = ...; image.id = "uuid:09233523-345b-4351-b623-5dsf35sgs5d6"; image.type = "image/jpeg"; image.options = soap_dime_option(soap, 0, "My wedding picture"); |
When either the id or type field values are non-NULL at run time,
the data will be serialized as a DIME attachment. The SOAP/XML message refers
to the attachments using href attributes. This generally works will with
SOAP RPC, because href attributes are permitted. However, with document/literal style the referencing mechanism must be explicitly defined
in the schema of the binary type. The gSOAP
declaration of an extended binary type is
struct ns__myBinaryDataType { unsigned char *__ptr; int __size; char *id; char *type; char *options; }; |
C++ programmers can use inheritance instead of textual extension required in C, as in
class xsd__base64Binary { unsigned char *__ptr; int __size; }; class ns__myBinaryDataType : xsd__base64Binary { char *id; char *type; char *options; }; |
This defines an extension of xsd:base64Binary, such that the data can be
serialized as DIME attachments using href attributes for referencing.
When a different attribute name is in fact used, it must be explicitly defined:
//gsoap WSref schema import: http://schemas.xmlsoap.org/ws/2002/04/reference/ struct ns__myBinaryDataType { unsigned char *__ptr; int __size; char *id; char *type; char *options; @char *WSref__location; }; |
The example above uses the location attribute defined in the
content reference schema, as defined in one of the vendor's specific
WSDL extensions for DIME
(http://www.gotdotnet.com/team/xml_wsspecs/dime/WSDL-Extension-for-DIME.htm).
When receiving DIME attachments, the DIME meta data and binary data content is
stored in binary data types only when the XML parts of the message uses
href attributes to refer to these attachments. The gSOAP toolkit may
support automatic (de)serialization with other user-defined (or WSDL-defined)
attributes in future releases.
Messages may contain binary data that references external resources not
provided as attachments. In that case, the __ptr field is NULL and the
id field refers to the external data source.
The dime_id_format attribute of the current gSOAP run-time context
can be set to the default format of DIME id fields. The format string MUST
contain a %d format specifier (or any other int-based format
specifier). The value of this specifier is a non-negative integer, with zero
being the value of the DIME attachment id for the SOAP message. For example,
struct soap soap; soap_init(&soap); soap.dime_id_format = "uuid:09233523-345b-4351-b623-5dsf35sgs5d6-%x"; |
As a result, all attachments with a NULL id field will use a
gSOAP-generated id value based on the format string.
Caution: Care must be taken not to introduce duplicate content id values,
when assigning content id values to the id fields of DIME extended binary data
types. Content ids must be unique.
15.4 Streaming DIME
Streaming DIME is achieved with callback functions to fetch and store data
during transmission. Three function callbacks for streaming DIME output and
three callbacks for streaming DIME input are available.
|
In addition, a void*user field in the struct soap data structure
is available to pass user-defined data to the callbacks. This way, you can set
soap.user to point to application data that the callbacks need such as a
file name for example.
The following example illustrates the client-side initialization of an image
attachment struct to stream a file into a DIME attachment:
int main() { struct soap soap; struct xsd__base64Binary image; FILE *fd; struct stat sb; soap_init(&soap); if (!fstat(fileno(fd), &sb) && sb.st_size > 0) { // because we can get the length of the file, we can stream it soap.fdimereadopen = dime_read_open; soap.fdimereadclose = dime_read_close; soap.fdimeread = dime_read; image.__ptr = (unsigned char*)fd; // must set to non-NULL (this is our fd handle which we need in the callbacks) image.__size = sb.st_size; // must set size } else { // don't know the size, so buffer it size_t i; int c; image.__ptr = (unsigned char*)soap_malloc(&soap, MAX_FILE_SIZE); for (i = 0; i < MAX_FILE_SIZE; i++) { if ((c = fgetc(fd)) == EOF) break; image.__ptr[i] = c; } fclose(fd); image.__size = i; } image.type = "image/jpeg"; image.options = soap_dime_option(&soap, 0, "My picture"); soap_call_ns__method(&soap, ...); ... } void *dime_read_open(struct soap *soap, void *handle, const char *id, const char *type, const char *options) { return handle; } void dime_read_close(struct soap *soap, void *handle) { fclose((FILE*)handle); } size_t dime_read(struct soap *soap, void *handle, char *buf, size_t len) { return fread(buf, 1, len, (FILE*)handle); } |
The following example illustrates the streaming of a DIME attachment into a file by a client:
int main() { struct soap soap; soap_init(&soap); soap.fdimewriteopen = dime_write_open; soap.fdimewriteclose = dime_write_close; soap.fdimewrite = dime_write; soap_call_ns__method(&soap, ...); ... } void *dime_write_open(struct soap *soap, const char *id, const char *type, const char *options) { FILE *handle = fopen("somefile", "wb"); if (!handle) { soap->error = SOAP_EOF; soap->errnum = errno; // get reason } return (void*)handle; } void dime_write_close(struct soap *soap, void *handle) { fclose((FILE*)handle); } int dime_write(struct soap *soap, void *handle, const char *buf, size_t len) { size_t nwritten; while (len) { nwritten = fwrite(buf, 1, len, (FILE*)handle); if (!nwritten) { soap->errnum = errno; // get reason return SOAP_EOF; } len -= nwritten; buf += nwritten; } return SOAP_OK; } |
Note that compression can be used with DIME to compress the entire message.
However, compression requires buffering to determine the HTTP content length
header, which cancels the benefits of streaming DIME. To avoid this, you should
use chunked HTTP (with the output-mode SOAP_IO_CHUNK flag) with
compression and streaming DIME. At the server side, when you set
SOAP_IO_CHUNK before calling soap_serve, gSOAP will
automatically revert to buffering (SOAP_IO_STORE flag is set). You can
check this flag with (soap->omode & SOAP_IO) == SOAP_IO_CHUNK to see
if the client accepts chunking. More information about streaming chunked DIME
can be found in Section 15.5.
Caution: The options field is a DIME-specific data structure,
consisting of a 4 byte header containing the option type info (hi byte, lo
byte), option string length (hi byte, lo byte), followed by a non-'\0'
terminated string. The gSOAP DIME handler recognizes one option at most.
15.5 Streaming Chunked DIME
gSOAP automatically handles inbound chunked DIME attachments (streaming or non-streaming). To transmit outbound DIME attachments, the attachment sizes MUST be determined in advance to calculate HTTP message length required to stream DIME over HTTP. However, gSOAP also supports the transmission of outbound chunked DIME attachments without prior determination of DIME attachment sizes when certain conditions are met. These conditions require either non-HTTP transport (use the output-mode SOAP_ENC_XML flag), or chunked HTTP transport (use the output-mode SOAP_IO_CHUNK flag). You can also use the SOAP_IO_STORE flag (which is also used automatically with compression to determine the HTTP content length header) but that cancels the benefits of streaming DIME. To stream chunked DIME, set the __size field of an attachment to zero and enable HTTP chunking. The DIME fdimeread callback then fetches data in chunks and it is important to fill the entire buffer unless the end of the data has been reached and the last chunk is to be send. That is, fdimeread should return the value of the last len parameter and fill the entire buffer buf for all chunks except the last.
15.6 WSDL Bindings for DIME Attachments
The wsdl2h WSDL parser recognizes DIME attachments and produces an annotated header file. Both open and closed layouts are supported for transmitting DIME attachments. For closed formats, all DIME attachments must be referenced from the SOAP message, e.g. using hrefs with SOAP encoding and using the application-specific reference attribute included in the base64Binary struct/class for doc/lit. The gSOAP compiler soapcpp2 does not produce a WSDL with DIME extensions. DIME is an older binary format that has no WSDL protocol support, unlike MIME and MTOM.
16 MTOM Attachments
MTOM (Message Transmission Optimization Mechanism) is a relatively new format
for transmitting attachments with SOAP messages (see
http://www.w3.org/TR/soap12-mtom). MTOM is a W3C working draft as of this
writing. MTOM attachments are essentially MIME attachments with standardized
mechanisms for cross referencing attachments from the SOAP body, which is
absent in (plain) MIME attachments and optional with DIME attachments.
Unlike the name suggests, the speed by which attached data is transmitted is
not increased compared to MIME, DIME, or even XML encoded base64 data (at least
the performance differences in gSOAP will be small). The advantage of the
format is the standardized attachment reference mechanism, which should improve
interoperability.
The MTOM specification mandates SOAP 1.2 and the use of the XOP namespace. The
XOP Include element xop:Include is used to reference attachment(s) from the SOAP message body.
Because references from within the SOAP message body to attachments are
mandatory with MTOM, the implementation of the serialization and
deserialization of MTOM
MIME attachments in gSOAP uses the extended binary type comparable to
DIME support in gSOAP. This binary type is predefined in the import/xop.h file:
//gsoap xop schema import: http://www.w3.org/2004/08/xop/include struct _xop__Include { unsigned char *__ptr; int __size; char *id; char *type; char *options; }; typedef struct _xop__Include _xop__Include; |
The additional id, type, and option fields
enable MTOM attachments for the data pointed to by __ptr of size __size. The process for sending and receiving MTOM XOP
attachments is fully automated.
The id field references the attachment (typically a content id CID or UUID). When set to NULL, gSOAP assigns a unique CID. The type
field specifies the required MIME type of the binary data, and the optional
options field can be used to piggy-back descriptive text with an attachment. The order of the
declaration of the fields is significant.
You can explicitly import the xop.h in your header file to use the MTOM attachments in your service, for example:
#import "import/soap12.h" /* alternatively, without the import above, use: //gsoap SOAP-ENV schema namespace: http://www.w3.org/2003/05/soap-envelope //gsoap SOAP-ENC schema namespace: http://www.w3.org/2003/05/soap-encoding */ #import "import/xop.h" #import "import/xmime5.h" //gsoap x schema namespace: http://my.first.mtom.net struct x__myData { _xop__Include xop__Include; // attachment @char *xmime5__contentType; // and its contentType }; int x__myMTOMtest(struct x__myData *in, struct x__myData *out); |
As you can see, there is really no difference between the specification of MTOM
and DIME attachments in a gSOAP header file. Except that you MUST use SOAP 1.2
and the xop__Include element.
When an instance of x__myDataType is serialized and either or both the
id and type fields are non-NULL, the data is transmitted as MTOM
MIME attachment if the SOAP_ENC_MTOM flag is set in the gSOAP's soap
struct context:
struct soap *soap = soap_new1(SOAP_ENC_MTOM); |
Without this flag, the attachments will be transmitted in DIME format
(Section 15). If your current clients and services are based on
non-streaming DIME attachments using the SOAP body reference mechanism (thus,
without using the soap_set_dime_attachment function) or plain base64
binary XML data elements, it is very easy to adopt MTOM by renaming the binary types to xop__Include and using the
SOAP_ENC_MTOM flag with the SOAP 1.2 namespace.
16.1 Generating MultipartRelated MIME Attachment Bindings in WSDL
To generate multipartRelated bindings in the WSDL file, use the //gsoap
... service method-mime-type directive (see also Section 19.2. The
directive can be repeated for each attachment you want to associate with a
method's request and response messages.
For example:
#import "import/soap12.h" #import "import/xop.h" #import "import/xmime5.h" //gsoap x schema namespace: http://my.first.mtom.net struct x__myData { _xop__Include xop__Include; // attachment @char *xmime5__contentType; // and its contentType }; //gsoap x service method-mime-type: myMTOMtest text/xml int x__myMTOMtest(struct x__myData *in, struct x__myData *out); |
The //gsoap x service method-mime-type directive indicates that this
operation accepts text/xml MIME attachments. See the SOAP-with-Attachment
specification for the MIME types to use (for example, */* is a wildcard).
If the operation has more than one attachment, just repeat this directive for
each attachment you want to bind to the operation.
To bind attachments only to the request message of an operation, use
//gsoap x service method-input-mime-type. Similarly, to bind attachments
only to the response message of an operation, use //gsoap x service
method-ouput-mime-type.
The wsdl2h WSDL parser recognizes MIME attachments and produces an
annotated header file. However, the ordering of MIME parts in the
multipartRelated elements is not reflected in the header file. Application
developers should adhere the standards and ensure that multipart/related
attachments are transmitted in compliance with the WSDL operation declarations.
16.2 Sending and Receiving MTOM Attachments
A receiver must be informed to recognize MTOM attachments by setting the
SOAP_ENC_MTOM flag of the gSOAP context. Otherwise, the regular MIME
attachment mechanism (SwA) will be used to store attachments.
When using wsdl2h to build clients and/or services, you should use the
typemap.dat file included in the distribution package. The
typemap.dat file defines the XOP namespace and XML MIME namespaces as
imported namespaces:
xop = < http://www.w3.org/2004/08/xop/include > xmime5 = < http://www.w3.org/2005/05/xmlmime > xmime4 = < http://www.w3.org/2004/11/xmlmime > |
The wsdl2h tool uses the typemap.dat file (see also option -t) to
convert WSDL into a gSOAP header file. In this case we don't want the
wsdl2h tool to read the XOP schema and translate it, since we have a
pre-defined _xop__Include element to handle XOP for MTOM. This
_xop__Include element is defined in xop.h. Therefore, the
bindings shown above will not translate the XOP and XML MIME schemas to code,
but generates #import statements instead:
#import "xop.h" #import "xmime5.h" |
The #import statements are only added for those namespaces that are
actually used by the service.
Let's take a look at an example.
The wsdl2h importer generates a header file with #import "xop.h" from a WSDL that references XOP, for example:
#import "xop.h" #import "xmime5.h" struct ns__Data { _xop__Include xop__Include; @char *xmime5__contentType; }; |
Suppose the WSDL defines an operation:
int ns__echoData(struct ns__Data *in, struct ns__Data *out); |
After generating the stubs/proxies with the soapcpp2 compiler, we can invoke the stub at the client side with:
struct soap *soap = soap_new1(SOAP_ENC_MTOM); struct ns__Data data; data.xop__Include.__ptr = (unsigned char*)"<b>Hello world!</b>"; data.xop__Include.__size = 20; data.xop__Include.id = NULL; // CID automatically generated by gSOAP engine data.xop__Include.type = "text/html"; // MIME type data.xop__Include.options = NULL; // no descriptive info added data.xmime5__contentType = "text/html"; // MIME type if (soap_call_ns__echoData(soap, endpoint, action, &data, &data)) soap_print_fault(soap, stderr); else printf("Got data\n"); soap_destroy(soap); // remove deserialized class instances soap_end(soap); // remove temporary and deserialized data soap_free(soap); // detach and free context |
Note that the xop__Include.type field must be set to transmit MTOM attachments, otherwise plain base64 XML will be used.
At the server side, we show an example of an operation handler that just copies the input data to output:
int ns__echoData(struct soap *soap, struct ns__Data *in, struct ns__data *out) { *out = *in; return SOAP_OK; } |
The server must use the SOAP_ENC_MTOM flag to initialize the soap struct to receive and send MTOM attachments.
16.3 Streaming MTOM/MIME
Streaming MTOM/MIME is achieved with callback functions to fetch and store data
during transmission. Three function callbacks for streaming MTOM/MIME output and
three callbacks for streaming MTOM/MIME input are available.
|
In addition, a void*user field in the struct soap data structure
is available to pass user-defined data to the callbacks. This way, you can set
soap.user to point to application data that the callbacks need such as a
file name for example.
The following example illustrates the client-side initialization of an
image
attachment struct to stream a file into a MTOM attachment without HTTP
chunking (HTTP streaming chunked MTOM transfer is presented in Section 16.5):
int main() { struct soap soap; struct xsd__base64Binary image; FILE *fd; struct stat sb; soap_init1(&soap, SOAP_ENC_MTOM); // mandatory to enable MTOM if (!fstat(fileno(fd), &sb) && sb.st_size > 0) { // because we can get the length of the file, we can stream it without chunking soap.fmimereadopen = mime_read_open; soap.fmimereadclose = mime_read_close; soap.fmimeread = mime_read; image.__ptr = (unsigned char*)fd; // must set to non-NULL (this is our fd handle which we need in the callbacks) image.__size = sb.st_size; // must set size } else { // don't know the size, so buffer it size_t i; int c; image.__ptr = (unsigned char*)soap_malloc(&soap, MAX_FILE_SIZE); for (i = 0; i < MAX_FILE_SIZE; i++) { if ((c = fgetc(fd)) == EOF) break; image.__ptr[i] = c; } fclose(fd); image.__size = i; } image.type = "image/jpeg"; // MIME type image.options = "This is my picture"; // description of object soap_call_ns__method(&soap, ...); ... } void *mime_read_open(struct soap *soap, void *handle, const char *id, const char *type, const char *description) { return handle; } void mime_read_close(struct soap *soap, void *handle) { fclose((FILE*)handle); } size_t mime_read(struct soap *soap, void *handle, char *buf, size_t len) { return fread(buf, 1, len, (FILE*)handle); } |
The following example illustrates the streaming of a MTOM/MIME attachment into a file by a client:
int main() { struct soap soap; soap_init(&soap); soap.fmimewriteopen = mime_write_open; soap.fmimewriteclose = mime_write_close; soap.fmimewrite = mime_write; soap_call_ns__method(&soap, ...); ... } void *mime_write_open(struct soap *soap, const char *id, const char *type, const char *description, enum soap_mime_encoding encoding) { FILE *handle = fopen("somefile", "wb"); // We ignore the MIME content transfer encoding here, but should check if (!handle) { soap->error = SOAP_EOF; soap->errnum = errno; // get reason } return (void*)handle; } void mime_write_close(struct soap *soap, void *handle) { fclose((FILE*)handle); } int mime_write(struct soap *soap, void *handle, const char *buf, size_t len) { size_t nwritten; while (len) { nwritten = fwrite(buf, 1, len, (FILE*)handle); if (!nwritten) { soap->errnum = errno; // get reason return SOAP_EOF; } len -= nwritten; buf += nwritten; } return SOAP_OK; } |
Note that compression can be used with MTOM/MIME to compress the entire message.
However, compression requires buffering to determine the HTTP content length
header, which cancels the benefits of streaming MTOM/MIME. To avoid this, you should
use chunked HTTP (with the output-mode SOAP_IO_CHUNK flag) with
compression and streaming MTOM/MIME. At the server side, when you set
SOAP_IO_CHUNK before calling soap_serve, gSOAP will
automatically revert to buffering (SOAP_IO_STORE flag is set). You can
check this flag with (soap->omode & SOAP_IO) == SOAP_IO_CHUNK to see
if the client accepts chunking. More information about streaming chunked MTOM/MIME
can be found in Section 16.5.
16.4 Redirecting Inbound MTOM/MIME Streams Based on SOAP Body Content
When it is preferable or required to redirect inbound MTOM/MIME attachment
streams based on SOAP message body content, where for example the names of the
resources are listed in the SOAP message body, an alternative mechanism must be
used. This mechanism can be used both at the client and server side.
Because the routing of the streams is accomplished with explicit function
calls, this method should only be used when required and should not be
considered optional. That is, when you enable this method, you MUST check for
pending MTOM/MIME attachments and handle them appropriately. This is true even
when you don't expect MTOM/MIME attachments in the payload, because the peer
may trick you by sending attachments anyway and you should be prepared to
accept or reject them.
The explicit MTOM/MIME streaming mechanism consists of three API functions:
|
Example client-side code in C:
struct soap *soap = soap_new1(SOAP_ENC_MTOM); soap_post_check_mime_attachments(soap); ... if (soap_call_ns__myMethod(soap, ...)) soap_print_fault(soap, stderr); // an error occurred else { if (soap_check_mime_attachments(soap)) { // attachments are present, channel is still open { do { ... // get data 'handle' from SOAP response and pass to callbacks ... // set the fmime callbacks, if needed struct soap_multipart *content = soap_get_mime_attachment(soap, (void*)handle); printf("Received attachment with id=%s and type=%s\n", content->id?content->id:"", content->type?content->type:""); } while (content); if (soap->error) soap_print_fault(soap, stderr); } } } ... soap_destroy(soap); soap_end(soap); soap_free(soap); // detach and free context |
The server-side service operations are implemented as usual, but with additional checks for MTOM/MIME attachments:
struct soap *soap = soap_new1(SOAP_ENC_MTOM); soap_post_check_mime_attachments(soap); ... soap_serve(soap); ... int ns__myMethod(struct soap *soap, ...) { ... // server-side processing logic if (soap_check_mime_attachments(soap)) { // attachments are present, channel is still open { do { ... // get data 'handle' from SOAP request and pass to callbacks ... // set the fmime callbacks, if needed struct soap_multipart *content = soap_get_mime_attachment(soap, (void*)handle); printf("Received attachment with id=%s and type=%s\n", content->id?content->id:"", content->type?content->type:""); } while (content); if (soap->error) return soap->error; } } ... // server-side processing logic return SOAP_OK; } |
16.5 Streaming Chunked MTOM/MIME
gSOAP automatically handles inbound chunked MTOM/MIME attachments (streaming or non-streaming). To transmit outbound MTOM/MIME attachments, the attachment sizes MUST be determined in advance to calculate HTTP message length required to stream MTOM/MIME over HTTP. However, gSOAP also supports the transmission of outbound chunked MTOM/MIME attachments without prior determination of MTOM/MIME attachment sizes when certain conditions are met. These conditions require either non-HTTP transport (use the output-mode SOAP_ENC_XML flag), or chunked HTTP transport (use the output-mode SOAP_IO_CHUNK flag). You can also use the SOAP_IO_STORE flag (which is also used automatically with compression to determine the HTTP content length header) but that cancels the benefits of streaming MTOM/MIME. To stream chunked MTOM/MIME, set the __size field of an attachment to zero and enable HTTP chunking. The MTOM/MIME fmimeread callback then fetches data in chunks of any size between 1 and the value of the len argument. The fmimeread callback should return 0 upon reaching the end of the data stream.