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.
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:[email protected]"; form2.href = "cid:[email protected]"; /* initialize and enable MIME */ soap_set_mime(soap, "MIME_boundary", "<[email protected]>"); /* add a base64 encoded tiff image (tiffImage points to base64 data) */ soap_set_mime_attachment(soap, tiffImage, tiffLen, SOAP_MIME_BASE64, "image/tiff", "<[email protected]>", 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", "<[email protected]>", 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", "<[email protected]>", 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.
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.
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).
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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
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; } |
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.