使用libxml进行soap消息封装与解析

来源:互联网 发布:excel数据验证 序列 编辑:程序博客网 时间:2024/05/08 17:46
当使用纯C客户端访问WCF服务时,最麻烦之处在于编写soap消息。如果单纯借助于字符串操作,则既费时又费力,还非常容易出错,因此必须采用更适合的手段。纯C的soap toolkit在sf上找到了csoap(http://sourceforge.net/projects/csoap/?source=directory),试用后发现有很大的局限性,虽然用户面对更少的xml细节,但无法完全按照想要的方式构建soap消息,主要体现在对Envelope/Header无法修改上。后又学习了gSoap(http://sourceforge.net/projects/gsoap2/),这是一个重量级的soap/wsdl框架,适用于从wsdl开始访问web service的方式(https://www.ibm.com/developerworks/cn/webservices/ws-soa-gsoap/),与本文的需求并不一致,遂作罢。重新梳理了一下,其实soap消息无非一个普通的xml格式文本,头部加上几行固定的字符串,核心问题还是xml解析。于是决定用最基本的libxml(http://xmlsoft.org/),w3c官方的工具,专治xml,至于多出的soap部分,copy&paste呗。libxml首先是个纯c的框架,其次解决底层的问题绰绰有余,另外足够简单。所以,应该找不出更好的办法了。

首先,利用libxml按指定格式构建一个xml文件,比如:

<?xmlversion="1.0" ?><s:Envelope xmlns:s="http://www.w3.org/2003/05/soap-envelope" xmlns:a="http://www.w3.org/2005/08/addressing">  <s:Header>    <a:Action s:mustUnderstand="1">http://tempuri.org/IMathService/echo</a:Action>    <a:To s:mustUnderstand="1">http://127.0.0.1:9998/MathService</a:To>  </s:Header>  <s:Body>    <echo xmlns="http://tempuri.org/">      <input>123</input>    </echo>  </s:Body></s:Envelope>

该xml文件的树形结构如下图所示:



按照这个结构,从根节点开始,依次生成各子、孙节点,代码如下:


#define BUF_SIZE 4096char* genSoapMsg(char*host, char*port, char*serviceImplementation, char*serviceContract, char*serviceMethod, char*param1, char*param1Val, char*param2, char*param2Val, char*param3, char*param3Val){    // host:port/service    char host_service[BUF_SIZE], action[BUF_SIZE];    // 记录树结构各节点的指针    xmlNodePtr nEnvelope, nHeader, nAction, cAction, nMessageID, cMessageID, nReplyTo, nAddress, cAddress, nTo, cTo,        nBody, nMethod, nParam1, cParam1, nParam2, cParam2, nParam3, cParam3;    // 保存XML文档的指针    xmlDocPtr doc;    // XML文档在内存中记录为字符串    xmlChar *xmlbuff;    // 用于xmlDocDumpFormatMemory调用, 记录写入XML文档的字符数    int buffersize;    // xmlChar字符串转为char字符串,strSoapMsg即为最终生成的soap消息字符串    char *buff,*soapBody;    char *soapHead1,*soapHead2,*soapHead3;    char soapHead[BUF_SIZE], soapMsg[BUF_SIZE];        sprintf(host_service,"http://%s:%s/%s", host, port, serviceImplementation);    sprintf(action,"http://tempuri.org/%s/%s", serviceContract, serviceMethod);    // 创建根结点s:Envelope    nEnvelope = xmlNewNode(NULL, BAD_CAST"s:Envelope");    xmlNewProp(nEnvelope, BAD_CAST"xmlns:s", BAD_CAST"http://www.w3.org/2003/05/soap-envelope");    xmlNewProp(nEnvelope, BAD_CAST"xmlns:a", BAD_CAST"http://www.w3.org/2005/08/addressing");    // 创建测试文档    doc = xmlNewDoc(BAD_CAST"1.0");    xmlDocSetRootElement(doc, nEnvelope);    // 创建子节点s:Header    nHeader = xmlNewNode(NULL, BAD_CAST"s:Header");    xmlAddChild(nEnvelope, nHeader);    nAction = xmlNewNode(NULL, BAD_CAST"a:Action");    xmlAddChild(nHeader, nAction);    xmlNewProp(nAction, BAD_CAST"s:mustUnderstand", BAD_CAST"1");    cAction = xmlNewText(BAD_CAST(action));    xmlAddChild(nAction, cAction);    nTo = xmlNewNode(NULL, BAD_CAST"a:To");    xmlAddChild(nHeader, nTo);    xmlNewProp(nTo, BAD_CAST"s:mustUnderstand", BAD_CAST"1");    cTo = xmlNewText(BAD_CAST(host_service));    xmlAddChild(nTo, cTo);    // 创建子节点s:Body    nBody = xmlNewNode(NULL, BAD_CAST"s:Body");    xmlAddChild(nEnvelope, nBody);    nMethod = xmlNewNode(NULL, BAD_CAST(serviceMethod));    xmlAddChild(nBody,nMethod);    xmlNewProp(nMethod, BAD_CAST"xmlns", BAD_CAST"http://tempuri.org/");    if (param1!= NULL)    {        nParam1 = xmlNewNode(NULL, BAD_CAST(param1));        xmlAddChild(nMethod, nParam1);        cParam1 = xmlNewText(BAD_CAST(param1Val));        xmlAddChild(nParam1, cParam1);    }    if (param2!= NULL)    {        nParam2 = xmlNewNode(NULL, BAD_CAST(param2));        xmlAddChild(nMethod, nParam2);        cParam2 = xmlNewText(BAD_CAST(param2Val));        xmlAddChild(nParam2, cParam2);    }    if (param3!= NULL)    {        nParam3 = xmlNewNode(NULL, BAD_CAST(param3));        xmlAddChild(nMethod, nParam3);        cParam3 = xmlNewText(BAD_CAST(param3Val));        xmlAddChild(nParam3, cParam3);    }    // 保存调试文件    xmlSaveFile(BAD_CAST"SopeRequest.xml", doc);    // 把文档内容写入字符串    xmlDocDumpFormatMemory(doc,&xmlbuff, &buffersize, 1);    buff = (char*) xmlbuff;    soapBody = strchr(buff,'\n')+ 1;    // soap消息头    soapHead1 = "POST /MathService HTTP/1.1\r\nContent-Type: application/soap+xml; charset=utf-8\r\nHost: ";    soapHead2 = "\r\nContent-Length: ";    soapHead3 = "\r\nExpect: 100-continue\r\nConnection: Close\r\n\r\n";    sprintf(soapHead,"%s%s:%s%s%d%s", soapHead1, host, port, soapHead2, strlen(soapBody), soapHead3);    sprintf(soapMsg,"%s%s", soapHead, soapBody);    xmlFreeDoc(doc);    return soapMsg;}

genSoapMsg函数的前五个参数分别是:host服务器地址,port端口号,serviceImplementation服务实现名,serviceContract服务接口名,serviceMethod服务方法名;后六个参数,表示传递给WCF服务方法的参数名称和参数值,也就是目前仅支持最多三个参数。后续这部分仍值得改进。这个函数实现了soap消息的构建,并存储在一个字符串中。调用如下:


soapMessage = genSoapMsg("127.0.0.1","9998", "MathService", "IMathService", "echo", "input", strSend,NULL, NULL, NULL,NULL);printf("发至WCF服务器: %s\n", strSend);


接下来完成soap消息的解析,代码如下:


xmlXPathObjectPtr getNodeSet(xmlDocPtr doc, xmlChar*xpath){     xmlXPathContextPtr context;     xmlXPathObjectPtr result;    context = xmlXPathNewContext(doc);    if (context== NULL)    {        printf("Error in xmlXPathNewContext\n");        return NULL;    }    xmlXPathRegisterNs(context, BAD_CAST"s", BAD_CAST"http://www.w3.org/2003/05/soap-envelope");    xmlXPathRegisterNs(context, BAD_CAST"a", BAD_CAST"http://www.w3.org/2005/08/addressing");    xmlXPathRegisterNs(context, BAD_CAST"t", BAD_CAST"http://tempuri.org/");    result = xmlXPathEvalExpression(xpath, context);    xmlXPathFreeContext(context);    if (result== NULL)    {        printf("Error in xmlXPathEvalExpression\n");        return NULL;    }    if(xmlXPathNodeSetIsEmpty(result->nodesetval))    {        xmlXPathFreeObject(result);        printf("No result\n");        return NULL;    }    return result; }void parseXmlString(char * xmlString, char * expression, char * keyword, char * result){    xmlDocPtr doc;    xmlChar *xpath = BAD_CAST(expression);    xmlXPathObjectPtr appResult;    xmlNodeSetPtr nodeSet;    xmlNodePtr cur;    int i;    doc = xmlParseMemory(xmlString, strlen(xmlString)+1);    // 保存调试文件    xmlSaveFile(BAD_CAST"SoapResponse.xml", doc);    appResult = getNodeSet(doc, xpath);    if (appResult!= NULL)    {        nodeSet = appResult->nodesetval;          for (i=0; i< nodeSet->nodeNr; i++)        {             cur = nodeSet->nodeTab[i];            cur = cur->xmlChildrenNode;              while (cur !=NULL)            {                if (!xmlStrcmp(cur->name, BAD_CAST(keyword)))                {                    strcpy(result,((char*)XML_GET_CONTENT(cur->xmlChildrenNode)));                    goto BREAK;                }                  cur = cur->next;            }        }BREAK:        xmlXPathFreeObject(appResult);        xmlCleanupParser();    }    xmlFreeDoc(doc);}

这里主要是运用xpath对xml文档(WCF服务器发回的soap响应消息)进行解析,得到<echoResult>节点(WCF服务方法调用返回)的内容,并以字符串返回。xpath是w3c标准的xml数据检索语言,相当于sql之于关系数据库。xpath的用法参见:http://xmlsoft.org/examples/index.html#XPath。需要注意的是,示例中节点名称都不带namespace(xmlns),而对于本文的应用,还需要额外增加注册ns的代码,即上述代码段中的14-16行。尤其是16行,注册了一个原xml中没有显式声明的ns:t,用于标识服务方法echo。主函数中调用parseXmlString的部分如下所示:


while (recv(sHost, buf, sizeof(buf), 0)> 0){    soapResponse = strchr(buf,'<');    ptr = strstr(buf,"</s:Envelope>")+ sizeof("</s:Envelope>")- 1;    *ptr ='\0';    parseXmlString(soapResponse,"/s:Envelope/s:Body/t:echoResponse","echoResult", soapParseResult);    printf("WCF服务器返回: %s\n", soapParseResult);}

注意第6行:t:echoResponse,如果不带t,则xpath无法正确解析。

至此,就完成了使用libxml进行soap消息封装与解析。
0 0
原创粉丝点击