最近给人写个gSoap的C++程序访问WCF服务,结果调用的时候没有返回0(SOAP_OK),而是415。无奈,无法直接在别人的机器上进行调试,那就自己搭一个简单的WCF服务吧。
测试用的WCF服务就是根据Visual Studio的向导生成的代码。
using System; using System.Collections.Generic; using System.Linq; using System.Runtime.Serialization; using System.ServiceModel; using System.ServiceModel.Web; using System.Text; namespace SimpleServer { // NOTE: You can use the "Rename" command on the "Refactor" menu to change the interface name "IService1" in both code and config file together. [ServiceContract] public interface IService1 { [OperationContract] string GetData(int value); [OperationContract] CompositeType GetDataUsingDataContract(CompositeType composite); // TODO: Add your service operations here } // Use a data contract as illustrated in the sample below to add composite types to service operations. [DataContract] public class CompositeType { bool boolValue = true; string stringValue = "Hello "; [DataMember] public bool BoolValue { get { return boolValue; } set { boolValue = value; } } [DataMember] public string StringValue { get { return stringValue; } set { stringValue = value; } } } }
using System; using System.Collections.Generic; using System.Linq; using System.Runtime.Serialization; using System.ServiceModel; using System.ServiceModel.Web; using System.Text; namespace SimpleServer { // NOTE: You can use the "Rename" command on the "Refactor" menu to change the class name "Service1" in code, svc and config file together. // NOTE: In order to launch WCF Test Client for testing this service, please select Service1.svc or Service1.svc.cs at the Solution Explorer and start debugging. public class Service1 : IService1 { public string GetData(int value) { return string.Format("You entered: {0}", value); } public CompositeType GetDataUsingDataContract(CompositeType composite) { if (composite == null) { throw new ArgumentNullException("composite"); } if (composite.BoolValue) { composite.StringValue += "Suffix"; } return composite; } } }
<?xml version="1.0" encoding="utf-8"?><wsdl:definitions name="Service1" targetNamespace="http://tempuri.org/" xmlns:wsdl="http://schemas.xmlsoap.org/wsdl/" xmlns:wsap="http://schemas.xmlsoap.org/ws/2004/08/addressing/policy" xmlns:wsa10="http://www.w3.org/2005/08/addressing" xmlns:tns="http://tempuri.org/" xmlns:msc="http://schemas.microsoft.com/ws/2005/12/wsdl/contract" xmlns:soapenc="http://schemas.xmlsoap.org/soap/encoding/" xmlns:wsx="http://schemas.xmlsoap.org/ws/2004/09/mex" xmlns:soap="http://schemas.xmlsoap.org/wsdl/soap/" xmlns:wsam="http://www.w3.org/2007/05/addressing/metadata" xmlns:wsa="http://schemas.xmlsoap.org/ws/2004/08/addressing" xmlns:wsp="http://schemas.xmlsoap.org/ws/2004/09/policy" xmlns:wsaw="http://www.w3.org/2006/05/addressing/wsdl" xmlns:soap12="http://schemas.xmlsoap.org/wsdl/soap12/" xmlns:wsu="http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-utility-1.0.xsd" xmlns:xsd="http://www.w3.org/2001/XMLSchema"><wsdl:types><xs:schema elementFormDefault="qualified" targetNamespace="http://tempuri.org/" xmlns:xs="http://www.w3.org/2001/XMLSchema"><xs:import namespace="http://schemas.datacontract.org/2004/07/SimpleServer"/><xs:element name="GetData"><xs:complexType><xs:sequence><xs:element minOccurs="0" name="value" type="xs:int"/></xs:sequence></xs:complexType></xs:element><xs:element name="GetDataResponse"><xs:complexType><xs:sequence><xs:element minOccurs="0" name="GetDataResult" nillable="true" type="xs:string"/></xs:sequence></xs:complexType></xs:element><xs:element name="GetDataUsingDataContract"><xs:complexType><xs:sequence><xs:element minOccurs="0" name="composite" nillable="true" type="q1:CompositeType" xmlns:q1="http://schemas.datacontract.org/2004/07/SimpleServer"/></xs:sequence></xs:complexType></xs:element><xs:element name="GetDataUsingDataContractResponse"><xs:complexType><xs:sequence><xs:element minOccurs="0" name="GetDataUsingDataContractResult" nillable="true" type="q2:CompositeType" xmlns:q2="http://schemas.datacontract.org/2004/07/SimpleServer"/></xs:sequence></xs:complexType></xs:element></xs:schema><xs:schema attributeFormDefault="qualified" elementFormDefault="qualified" targetNamespace="http://schemas.microsoft.com/2003/10/Serialization/" xmlns:xs="http://www.w3.org/2001/XMLSchema" xmlns:tns="http://schemas.microsoft.com/2003/10/Serialization/"><xs:element name="anyType" nillable="true" type="xs:anyType"/><xs:element name="anyURI" nillable="true" type="xs:anyURI"/><xs:element name="base64Binary" nillable="true" type="xs:base64Binary"/><xs:element name="boolean" nillable="true" type="xs:boolean"/><xs:element name="byte" nillable="true" type="xs:byte"/><xs:element name="dateTime" nillable="true" type="xs:dateTime"/><xs:element name="decimal" nillable="true" type="xs:decimal"/><xs:element name="double" nillable="true" type="xs:double"/><xs:element name="float" nillable="true" type="xs:float"/><xs:element name="int" nillable="true" type="xs:int"/><xs:element name="long" nillable="true" type="xs:long"/><xs:element name="QName" nillable="true" type="xs:QName"/><xs:element name="short" nillable="true" type="xs:short"/><xs:element name="string" nillable="true" type="xs:string"/><xs:element name="unsignedByte" nillable="true" type="xs:unsignedByte"/><xs:element name="unsignedInt" nillable="true" type="xs:unsignedInt"/><xs:element name="unsignedLong" nillable="true" type="xs:unsignedLong"/><xs:element name="unsignedShort" nillable="true" type="xs:unsignedShort"/><xs:element name="char" nillable="true" type="tns:char"/><xs:simpleType name="char"><xs:restriction base="xs:int"/></xs:simpleType><xs:element name="duration" nillable="true" type="tns:duration"/><xs:simpleType name="duration"><xs:restriction base="xs:duration"><xs:pattern value="\-?P(\d*D)?(T(\d*H)?(\d*M)?(\d*(\.\d*)?S)?)?"/><xs:minInclusive value="-P10675199DT2H48M5.4775808S"/><xs:maxInclusive value="P10675199DT2H48M5.4775807S"/></xs:restriction></xs:simpleType><xs:element name="guid" nillable="true" type="tns:guid"/><xs:simpleType name="guid"><xs:restriction base="xs:string"><xs:pattern value="[\da-fA-F]{8}-[\da-fA-F]{4}-[\da-fA-F]{4}-[\da-fA-F]{4}-[\da-fA-F]{12}"/></xs:restriction></xs:simpleType><xs:attribute name="FactoryType" type="xs:QName"/><xs:attribute name="Id" type="xs:ID"/><xs:attribute name="Ref" type="xs:IDREF"/></xs:schema><xs:schema elementFormDefault="qualified" targetNamespace="http://schemas.datacontract.org/2004/07/SimpleServer" xmlns:xs="http://www.w3.org/2001/XMLSchema" xmlns:tns="http://schemas.datacontract.org/2004/07/SimpleServer"><xs:complexType name="CompositeType"><xs:sequence><xs:element minOccurs="0" name="BoolValue" type="xs:boolean"/><xs:element minOccurs="0" name="StringValue" nillable="true" type="xs:string"/></xs:sequence></xs:complexType><xs:element name="CompositeType" nillable="true" type="tns:CompositeType"/></xs:schema></wsdl:types><wsdl:message name="IService1_GetData_InputMessage"><wsdl:part name="parameters" element="tns:GetData"/></wsdl:message><wsdl:message name="IService1_GetData_OutputMessage"><wsdl:part name="parameters" element="tns:GetDataResponse"/></wsdl:message><wsdl:message name="IService1_GetDataUsingDataContract_InputMessage"><wsdl:part name="parameters" element="tns:GetDataUsingDataContract"/></wsdl:message><wsdl:message name="IService1_GetDataUsingDataContract_OutputMessage"><wsdl:part name="parameters" element="tns:GetDataUsingDataContractResponse"/></wsdl:message><wsdl:portType name="IService1"><wsdl:operation name="GetData"><wsdl:input wsaw:Action="http://tempuri.org/IService1/GetData" message="tns:IService1_GetData_InputMessage"/><wsdl:output wsaw:Action="http://tempuri.org/IService1/GetDataResponse" message="tns:IService1_GetData_OutputMessage"/></wsdl:operation><wsdl:operation name="GetDataUsingDataContract"><wsdl:input wsaw:Action="http://tempuri.org/IService1/GetDataUsingDataContract" message="tns:IService1_GetDataUsingDataContract_InputMessage"/><wsdl:output wsaw:Action="http://tempuri.org/IService1/GetDataUsingDataContractResponse" message="tns:IService1_GetDataUsingDataContract_OutputMessage"/></wsdl:operation></wsdl:portType><wsdl:binding name="BasicHttpBinding_IService1" type="tns:IService1"><soap:binding transport="http://schemas.xmlsoap.org/soap/http"/><wsdl:operation name="GetData"><soap:operation soapAction="http://tempuri.org/IService1/GetData" style="document"/><wsdl:input><soap:body use="literal"/></wsdl:input><wsdl:output><soap:body use="literal"/></wsdl:output></wsdl:operation><wsdl:operation name="GetDataUsingDataContract"><soap:operation soapAction="http://tempuri.org/IService1/GetDataUsingDataContract" style="document"/><wsdl:input><soap:body use="literal"/></wsdl:input><wsdl:output><soap:body use="literal"/></wsdl:output></wsdl:operation></wsdl:binding><wsdl:service name="Service1"><wsdl:port name="BasicHttpBinding_IService1" binding="tns:BasicHttpBinding_IService1"><soap:address location="http://localhost:15418/Service1.svc"/></wsdl:port></wsdl:service></wsdl:definitions>
@echo off ..\..\gsoap-2.8\gsoap\bin\win32\wsdl2h.exe -v -I..\..\gsoap-2.8\gsoap -o Service1.h Service1.wsdl 2>>soap.log @pause ..\..\gsoap-2.8\gsoap\bin\win32\soapcpp2.exe -C -j -I..\..\gsoap-2.8\gsoap;..\..\gsoap-2.8\gsoap\import;..\..\gsoap-2.8\gsoap\custom Service1.h 2>>soap.log @pause
客户端代码:
#include <iostream> #include <string> #include "soapBasicHttpBinding_USCOREIService1Proxy.h" #include "BasicHttpBinding_USCOREIService1.nsmap" int main() { BasicHttpBinding_USCOREIService1Proxy proxy; int value = 2; _tempuri__GetData request; request.value = &value; _tempuri__GetDataResponse response; int ret = proxy.GetData(&request, &response); if (ret == SOAP_OK) { std::cout << *response.GetDataResult << std::endl; } else { proxy.soap_stream_fault(std::cerr); } return 0; }
Error 415 fault: SOAP-ENV:Server[no subcode] "HTTP Error" Detail: HTTP/1.1 415 Cannot process the message because the content type 'application/soap+xml; charset=utf-8; action="http://tempuri.org/IService1/GetData"' wa s not the expected type 'text/xml; charset=utf-8'.
成功复现之前的错误。经过一些搜索,得到了答案。发送的请求中 content type 应该是 'text/xml; charset=utf-8',现在却是 'application/soap+xml; charset=utf-8; action="http://tempuri.org/IService1/GetData"‘。
修正的话,需要在生成C++代码是按照SOAP1.1标准,执行如下命令:
..\..\gsoap-2.8\gsoap\bin\win32\soapcpp2.exe -1 -C -j -I..\..\gsoap-2.8\gsoap;..\..\gsoap-2.8\gsoap\import;..\..\gsoap-2.8\gsoap\custom Service1.h 2>>soap.log
重新编译之后,能够成功调用,输出
You entered: 2
后来查找有什么替代的方法,找到了 WWSAPI 的方法,缺点是不能跨平台,这里记录一下实现的方法。
首先生成WSDL对应的C++封装文件:
Wsutil.exe /wsdl:Service1.wsdl /string:WS_STRING
//------------------------------------------------------------ // Copyright (C) Microsoft. All rights reserved. //------------------------------------------------------------ #ifndef UNICODE #define UNICODE #endif #include <windows.h> #include <stdio.h> #include <process.h> #include <string.h> #include "WebServices.h" #include "Service1.wsdl.h" // Print out rich error info void PrintError(HRESULT errorCode, WS_ERROR* error) { wprintf(L"Failure: errorCode=0x%lx\n", errorCode); if (errorCode == E_INVALIDARG || errorCode == WS_E_INVALID_OPERATION) { // Correct use of the APIs should never generate these errors wprintf(L"The error was due to an invalid use of an API. This is likely due to a bug in the program.\n"); DebugBreak(); } HRESULT hr = NOERROR; if (error != NULL) { ULONG errorCount; hr = WsGetErrorProperty(error, WS_ERROR_PROPERTY_STRING_COUNT, &errorCount, sizeof(errorCount)); if (FAILED(hr)) { goto Exit; } for (ULONG i = 0; i < errorCount; i++) { WS_STRING string; hr = WsGetErrorString(error, i, &string); if (FAILED(hr)) { goto Exit; } wprintf(L"%.*s\n", string.length, string.chars); } } Exit: if (FAILED(hr)) { wprintf(L"Could not get error string (errorCode=0x%lx)\n", hr); } } // Main entry point int __cdecl wmain(int argc, __in_ecount(argc) wchar_t **argv) { UNREFERENCED_PARAMETER(argc); UNREFERENCED_PARAMETER(argv); HRESULT hr = NOERROR; WS_ERROR* error = NULL; WS_CHANNEL* channel = NULL; WS_HEAP* heap = NULL; WS_ENDPOINT_ADDRESS address; WS_STRING serviceUrl = WS_STRING_VALUE(L"http://localhost:15418/Service1.svc"); // Create an error object for storing rich error information hr = WsCreateError( NULL, 0, &error); if (FAILED(hr)) { goto Exit; } // Create a heap to store deserialized data hr = WsCreateHeap( /*maxSize*/ 2048, /*trimSize*/ 512, NULL, 0, &heap, error); if (FAILED(hr)) { goto Exit; } //// Create a channel hr = WsCreateChannel( WS_CHANNEL_TYPE_REQUEST, WS_HTTP_CHANNEL_BINDING, NULL, 0, NULL, &channel, error); if (FAILED(hr)) { goto Exit; } // Initialize address of service address.url = serviceUrl; address.headers = NULL; address.extensions = NULL; address.identity = NULL; // Open channel to address hr = WsOpenChannel(channel, &address, NULL, error); if (FAILED(hr)) { goto Exit; } WS_SERVICE_PROXY * serviceProxy; hr = BasicHttpBinding_IService1_CreateServiceProxy( NULL, NULL, 0, &serviceProxy, error); if (FAILED(hr)) { goto Exit; } hr = WsResetMessage(requestMessage, error); hr = WsOpenServiceProxy(serviceProxy, &address, NULL, error); if (FAILED(hr)) { goto Exit; } hr = WsResetMessage(requestMessage, error); // Send some request-replies for (int i = 0; i < 100; i++) { WS_STRING res; hr = BasicHttpBinding_IService1_GetData(serviceProxy, 2, &res, heap, NULL, 0, NULL, error); if (FAILED(hr)) { goto Exit; } // Print out confirmation contents wprintf(L"The Response is %.*s\n", res.length, res.chars); // Reset the heap hr = WsResetHeap(heap, error); if (FAILED(hr)) { goto Exit; } } Exit: if (FAILED(hr)) { // Print out the error PrintError(hr, error); } fflush(stdout); if (channel != NULL) { // Close the channel WsCloseChannel(channel, NULL, error); } if (channel != NULL) { WsFreeChannel(channel); } if (error != NULL) { WsFreeError(error); } if (heap != NULL) { WsFreeHeap(heap); } fflush(stdout); return SUCCEEDED(hr) ? 0 : -1; }
参考链接:
http://www.blinnov.com/en/2008/01/22/wcf-service-unmanaged-client/
http://coab.wordpress.com/2009/10/15/calling-wcf-services-from-a-linux-c-client-using-gsoap/