當我們要在 xml 中傳送一組binary資料時,一般的作法是將此資料先使用Base64編碼,再送出,如此會使得此資料大小增加為原本的 4/3 倍。
為此,W3C定義了MTOM方法,當傳送大量的資料時,可以減少所需的資料傳輸量。
MTOM 的原理是使用 XOP (XML-binary Optimized Packaging) 來封裝 binary 資料,目前 XOP 只定義使用一種封裝方法,就是 MIME Multipart/Related。
以下將介紹一個 MTOM 的例子,並介紹如何利用 gSOAP 取得 MTOM 資料
一、MTOM 例子
我們從一個簡單的例子來了解 XOP 的作法,(此例子來源為http://www.w3.org/TR/2005/REC-xop10-20050125/)
原始的 XML,此處 binary 資料使用 base64 編碼
1 <m:data xmlns:m='http://example.org/stuff'> 2 <m:photo>/aWKKapGGyQ=</m:photo> 3 </m:data>
封裝為 XOP package,此處 binary 資料使用 MIME 封裝
1 MIME-Version: 1.0 2 Content-Type: Multipart/Related;boundary=MIME_boundary; 3 type="application/xop+xml"; 4 start="<mymessage.xml@example.org>"; 5 start-info="text/xml" 6 Content-Description: An XML document with my pic and sig in it 7 8 --MIME_boundary 9 Content-Type: application/xop+xml; 10 charset=UTF-8; 11 type="text/xml" 12 Content-Transfer-Encoding: 8bit 13 Content-ID: <mymessage.xml@example.org> 14 15 <m:data xmlns:m='http://example.org/stuff'> 16 <m:photo><xop:Include 17 xmlns:xop='http://www.w3.org/2004/08/xop/include' 18 href='cid:http://example.org/me.png'/></m:photo> 19 </m:data> 20 21 --MIME_boundary 22 23 Content-Type: image/png 24 Content-Transfer-Encoding: binary 25 Content-ID: <http://example.org/me.png> 26 27 // binary octets for png 28 29 --MIME_boundary
封裝後,第一個 multipart 為 "application/xop+xml",並且此處的 "start-info" 要指向 XML,後續其他的multipart就可以用來放置各種資料,然後透過 cid 來找到正確的 multipart。
注意: 就我個人的認知,此處的 cid 因為要符合 URI[RFC 2392]的定義,所以應該要將"cid:http://example.org/me.png" 編碼為 "cid:http%3a%2f%2fexample.org%2fme.png"才對
二、使用 gSOAP 接收 MTOM 資料
GSOAP 的支援,原文請參考 http://www.cs.fsu.edu/~engelen/soapdoc2.html#tth_sEc16,以下幫忙做簡單翻譯。
當使用 wsdl2h 進行 code generation 時,需要在 typemap.dat 中加入下列 namespace
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 >
之後在產生的 header file中,將可以看到類似以下的 XOP 資料結構,或者使用者也可以自行進行定義新的資料結構
1 #import "import/xop.h" 2 struct x__myData 3 { 4 _xop__Include xop__Include; // attachment 5 @char *xmime5__contentType; // and its contentType 6 };
當要使用 GSOAP 時,需要告知將使用 MTOM
struct soap *soap = soap_new1(SOAP_ENC_MTOM);
注意:若沒有正確的設定 MIME Type, 則 GSOAP 會直接回 base64 而非使用 MTOM方式封裝資料。
Note that the xop__Include.type field must be set to transmit MTOM attachments, otherwise plain base64 XML will be used.
三、ONVIF的使用
參考 http://www.cs.fsu.edu/~engelen/soap.html 定義的 ONVIF 方式,code generation之後可以產生以下的資料結構,可用來放置 XOP 。
1 #import "import/xop.h" 2 struct tt__AttachmentData 3 { 4 //xop__Include xop__Include; // attachment 5 _xop__Includexop__Include; // attachment 6 @char *xmime__contentType; // and its contentType 7 };
此時,我們可以使用免費的 ONVIF Device Manager 工具,透過利用 ONVIF 定義的 UpgradeSystemFirmware command 命令來測試是否 MTOM 有正常運作。
測試結果發現程式可以正確的呼叫 __tds__UpgradeSystemFirmware() stub function, 但是 _xop__Include_ 並沒有正確的產生。
此時可以參考 gSOAP source code 內的 \samples\mtom-stream\mtom-stream-test.c,新增下列三個 callback
1 void *mime_server_write_open(struct soap *soap, void *handle, const char *id, const char *type, const char *description, enum soap_mime_encoding encoding); 2 void mime_server_write_close(struct soap *soap, void *handle); 3 int mime_server_write(struct soap *soap, void *handle, const char *buf, size_t len);
並且在 init soap 之後,設定對應的Callback
1 soap_init1(&soap, SOAP_ENC_MTOM); 2 soap.fmimewriteopen = mime_server_write_open; 3 soap.fmimewriteclose = mime_server_write_close; 4 soap.fmimewrite = mime_server_write;
重新編譯後再次測試,此時便可以發現 _xop__Include_ 已經正確的產生,並且對應的物件也已經存入系統磁碟中
注意:
測試時發現,若檔案過小,例如只有128bytes,則ONVIF Device Manager會直接將資料使用base64編碼後,放入XML內。若檔案稍大,例如2048bytes,便會使用 MTOM的方法。
參考資料
- http://www.w3.org/TR/soap12-mtom/
- http://www.w3.org/TR/xop10/ (XML-binary Optimized Packaging)
- mtom-stream-test.c
- http://www.geekhideout.com/urlcode.shtml