gSOAP MTOM
前言
需要准备的知识: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 文件中定义:
[blockquote]
//gsoap xop schema import: http://www.w3.org/2004/08/xop/includestruct _xop__Include{unsigned char *__ptr;int __size;char *id;char *type;char *options;};typedef struct _xop__Include _xop__Include;
[/blockquote]
除了 id,type还有两个选项__ptr、__size可用。发送和接受MTOPM XOP附件的过程是完全自动的。id关联附件(典型的内容标识为CID或UUID)。当 id 为空=NULL时Gsoap会分配一个唯一的CID。type 字段指明二进制数据的MIME类型,同时可选选项可以用来传输附件的附加说明。结构体的字段声明顺序时敏感的(也就是结构的变量声明顺序不能变化).
可以声明自己的数据结构体包含 xop.h MTOM的附件定义,例如:
[blockquote]
#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.netstruct x__myData{ _xop__Include xop__Include; // attachment @char *xmime5__contentType; // and its contentType};int x__myMTOMtest(struct x__myData *in, struct x__myData *out);
[/blockquote]
如上所示,在MTOM和DIME的附件在gSOAP的头文件定义中除了MTOM附件必须时SOAP 1.2 和是用 xop__Include 元素外,没有任何区别。
当 x_myData 实例序列化时,id 和 type 字段都不能为NULL,gSOAP的soap结构内容的标识为 SOAP_ENC_MTOM 时附件就会以 MTOM MIME附件方式传输。
[blockquote]
struct soap *soap = soap_new1(SOAP_ENC_MTOM);
[/blockquote]
不设置这个标识附加将以 DIME 的方式传输。
如果你目前的客户端和服务都是基于非流 DIME 附件使用SOAP正文引用机制(因此,没有使用soap_set_dime_attachment函数)或纯base64二进制XML数据元素,很容易采用MTOM通过重命名xop__Include和使用的二进制类型 SOAP_ENC_MTOM 标识与SOAP 1.2名称空间。
第二节 流动式接收MTOM/MIME
流动式接收MTOM/MIME是用回调函数的方式实现附件传输期间的数据抓取和存储。三个回调函数实现流动式接收MTOM/MIME的输出(写),三个回调函数实现流动式接收MTOM/MIME的输入(读)。
如下是输入(读取)附件的三个回调函数:
[blockquote]
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)
[/blockquote]
如下是输出(写入)附加的三个回调函数:
[blockquote]
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)
[/blockquote]
此外,一个void *user字段结构soap数据结构可以将用户定义的数据传递给回调函数。通过这种方式,您可以设置soap。用户指向应用程序数据的回调需要,例如一个文件名。
下面的例子说明了客户端初始化一个图像附件结构流文件为MTOM附件没有HTTP分块:
[blockquote]
int main(){struct soap soap;struct xsd__base64Binary image; FILE *fd;struct stat sb; soap_init1(&soap, SOAP_ENC_MTOM); // mandatory to enable MTOMif (!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 = 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);}
[/blockquote]
面的例子说明了MTOM / MIME的流由一个客户端存储在一个文件中:
[blockquote]
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 checkif (!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 reasonreturn SOAP_EOF; } len -= nwritten; buf += nwritten; }return SOAP_OK;}
[/blockquote]
服务器端的文件管理同样取决与回调函数的实现,如下是gSOAP提供的例子程序 mtom-stream 的服务端写入的代码
[blockquote]
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;}
[/blockquote]
如上红色的代码使用 C 函数tempnam产生了一个临时文件名用于保存附件,在真实的实现中可以根据MIME类型对附件进行分类管理。
第三节 使用SoapUI测试MTOM
如下图在Attachments中添加若干附件,在soap请求的中的 xop:include 的属性href中通过 cid 引用附件,
请求的结果为:
如上对的例子是以MTOM方式请求,返回的结果是base64编码的二进制格式。同样可以使用base64编码的消息获取MTOM方式的附件消息格式,如下:
第四节 用fidder获得SoapUI报文
用Fidder获取SoapUI的报文需要用的两个软甲的代理功能。
4.1 SoapUI设置
step 1: 打开菜单 File--->Preferences
step 2: 选择Proxy Setting选项卡进行如下设置:
4.2 Fidder设置
step 1: Tools-》 Options
step 2:打开选项卡 Connections
原理上就是SoapUI通过8888端口为代理对外发送请求,而Fidder监听8888端口的代理事件。如下为用Fidder截取的SoapUI发送的报文: