需要准備的知識:wsdl,soap,gSOAP,C++,fidder。
MTOM(Message Transmission Optimization Mechanism)是一種新的(相對MIME、DIME)的SOAP消息傳輸附件的格式。MTOM附件本質上是在SOAP body標簽中引用的標准MIME附件,可以不用MIME附件而是用DIME附件。
MTOM在SOAP 1.2中實現,同時是用XOP命名空間。XOP Include元素xop:include(在SOAP body標簽中)用來引用附件(可以又多個附件)。
由於用MTOM方式強制規定SOAP 消息的body需要引用附件,GSoap是用類似DIME的實現方式實現MTOM和MIME的二進制附件的序列化和反序列化。這個二進制結構事前在 import/xop.h 文件中定義:
//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;
除了 id,type 還有兩個選項__ptr、__size可用。發送和接受MTOPM XOP附件的過程是完全自動的。id 關聯附件(典型的內容標識為CID或UUID)。當 id 為空=NULL時Gsoap會分配一個唯一的CID。type 字段指明二進制數據的MIME類型,同時可選選項可以用來傳輸附件的附加說明。結構體的字段聲明順序時敏感的(也就是結構的變量聲明順序不能變化).
可以聲明自己的數據結構體包含 xop.h MTOM的附件定義,例如:
#import ïmport/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 ïmport/xop.h"
#import ïmport/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);
如上所示,在MTOM和DIME的附件在gSOAP的頭文件定義中除了MTOM附件必須時SOAP 1.2 和是用 xop__Include 元素外,沒有任何區別。
當 x_myData 實例序列化時,id 和 type 字段都不能為NULL,gSOAP的soap結構內容的標識為 SOAP_ENC_MTOM 時附件就會以 MTOM MIME附件方式傳輸。
struct soap *soap = soap_new1(SOAP_ENC_MTOM);
不設置這個標識附加將以 DIME 的方式傳輸。
如果你目前的客戶端和服務都是基於非流 DIME 附件使用SOAP正文引用機制(因此,沒有使用soap_set_dime_attachment函數)或純base64二進制XML數據元素,很容易采用MTOM通過重命名xop__Include和使用的二進制類型 SOAP_ENC_MTOM 標識與SOAP 1.2名稱空間。
流動式接收MTOM/MIME是用回調函數的方式實現附件傳輸期間的數據抓取和存儲。三個回調函數實現流動式接收MTOM/MIME的輸出(寫),三個回調函數實現流動式接收MTOM/MIME的輸入(讀)。
如下是輸入(讀取)附件的三個回調函數:
void *(*soap.fmimereadopen)(struct soap *soap, void *handle,const char *id, const char *type, const char *description)
size_t (*soap.fmimeread)(struct soap *soap, void *handle, char *buf, size_t len)
void(*soap.fmimereadclose)(struct soap *soap, void *handle)
如下是輸出(寫入)附加的三個回調函數:
void *(*soap.fmimewriteopen)(struct soap *soap, void *handle,const char *id, const char *type, const char *description,enum soap_mime_encoding encoding)
int (*soap.fmimewrite)(struct soap *soap, void *handle,const char *buf, size_t len)
void(*soap.fmimewriteclose)(struct soap *soap, void *handle)
此外,一個void *user字段結構soap數據結構可以將用戶定義的數據傳遞給回調函數。通過這種方式,您可以設置soap。用戶指向應用程序數據的回調需要,例如一個文件名。
下面的例子說明了客戶端初始化一個圖像附件結構流文件為MTOM附件沒有HTTP分塊:
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);
}
面的例子說明了MTOM / MIME的流由一個客戶端存儲在一個文件中:
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;
}
服務器端的文件管理同樣取決與回調函數的實現,如下是gSOAP提供的例子程序 mtom-stream 的服務端寫入的代碼
void *mime_server_write_open(struct soap *soap, void *unused_handle, const char *id, const char *type, const char *description, enum soap_mime_encoding encoding)
{
/* Note: the 'unused_handle' is always NULL */
/* Return NULL without setting soap->error if we don't want to use the streaming callback for this DIME attachment */
const char *file;
struct mime_server_handle *handle = (struct mime_server_handle *)soap_malloc(soap, sizeof(struct mime_server_handle));
if (!handle)
{ soap->error = SOAP_EOM;
return NULL;
}
/* Create a new file */
file = tempnam(TMPDIR, "data");/* The file name is also the key */
handle->key = soap_strdup(soap, file);
handle->fd = fopen(file, "wb");
free((void*)file);
if (!handle->fd)
{ soap->error = soap_sender_fault(soap, "Cannot save data to file", handle->key);
soap->errnum = errno; /* get reason */
return NULL;
}
fprintf(stderr, "Saving file %s type %s\n", handle->key, type?type:"");
return (void*)handle;
}
如上紅色的代碼使用 C 函數tempnam產生了一個臨時文件名用於保存附件,在真實的實現中可以根據MIME類型對附件進行分類管理。
如下圖在Attachments中添加若干附件,在soap請求的中的 xop:include 的屬性href中通過 cid 引用附件,
請求的結果為:
如上對的例子是以MTOM方式請求,返回的結果是base64編碼的二進制格式。同樣可以使用base64編碼的消息獲取MTOM方式的附件消息格式,如下:
用Fidder獲取SoapUI的報文需要用的兩個軟甲的代理功能。
step 1: 打開菜單 File--->Preferences