最近给人写个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;
}
}
}
@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
#include
#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
#include
#include
#include
#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/