经过前几节的讲解,相信大家都能够熟练地开发gSOAP应用程序,甚至跨平台也不是问题。但是,诸如stock、weather、exchange这些应用都是面向大众提供的免费资源,不是企业级的应用,绝大多数都不需要用户认证。而那些商业化的应用却恰恰相反,大部分都要求客户端提供这样那样的验证。
由于有认证的免费资源实在难找,我只好把公司正在使用的一个服务的wsdl裁剪一下,拿到这里作为实例,裁剪后的wsdl只保留一个echo接口,顾名思义,就是客户端送什么字符串上来,服务端就返回同样的字符串。这个wsdl如下(业务相关的网址和end point均已作了特别处理):
< s0:definitions name ="ServicesDefinitions" targetNamespace ="http://echo.rsecure.com/ECHO" xmlns ="" xmlns:s0 ="http://schemas.xmlsoap.org/wsdl/" xmlns:s1 ="http://echo.rsecure.com/ECHO" xmlns:s2 ="http://schemas.xmlsoap.org/wsdl/soap/" >
< s0:types >
< xs:schema attributeFormDefault ="unqualified" elementFormDefault ="qualified" targetNamespace ="http://echo.rsecure.com/ECHO" xmlns:s0 ="http://schemas.xmlsoap.org/wsdl/" xmlns:s1 ="http://echo.rsecure.com/ECHO" xmlns:s2 ="http://schemas.xmlsoap.org/wsdl/soap/" xmlns:xs ="http://www.w3.org/2001/XMLSchema" >
< xs:element name ="echo" >
< xs:complexType >
< xs:sequence >
< xs:element name ="EchoMessage" type ="xs:string" />
</ xs:sequence >
</ xs:complexType >
</ xs:element >
< xs:element name ="echoResponse" >
< xs:complexType >
< xs:sequence >
< xs:element name ="Echo" type ="xs:string" />
</ xs:sequence >
</ xs:complexType >
</ xs:element >
</ xs:schema >
</ s0:types >
< s0:message name ="echo" >
< s0:part element ="s1:echo" name ="parameters" />
</ s0:message >
< s0:message name ="echoResponse" >
< s0:part element ="s1:echoResponse" name ="Echo" />
</ s0:message >
< s0:portType name ="LMIAPort" >
< s0:operation name ="echo" parameterOrder ="parameters" >
< s0:input message ="s1:echo" />
< s0:output message ="s1:echoResponse" />
</ s0:operation >
</ s0:portType >
< s0:binding name ="ServicesSoapBinding" type ="s1:LMIAPort" >
< s2:binding style ="document" transport ="http://schemas.xmlsoap.org/soap/http" />
< s0:operation name ="echo" >
< s2:operation style ="document" />
< s0:input >
< s2:body parts ="parameters" use ="literal" />
</ s0:input >
< s0:output >
< s2:body parts ="Echo" use ="literal" />
</ s0:output >
</ s0:operation >
</ s0:binding >
< s0:service name ="Services" >
< s0:port binding ="s1:ServicesSoapBinding" name ="lmiAPort" >
< s2:address location ="https://localhost:6883" />
</ s0:port >
</ s0:service >
</ s0:definitions >
在gsoap-2.7/gsoap/wsdl/目录下建立两个目录:echo和echo_server,按照前几节的方法分别建立gSOAP客户端和服务端。客户端与前几节的相比,首先是增加了soap_ssl_client_context处理HTTPS协议。其次,本案例使用的是基本认证(Basic Authentication),需要在soap变量初始化之后给出用户名和密码。
soap_init( & soap);
soap.userid = argv[ 1 ];
soap.passwd = argv[ 2 ];
客户端完整程序如下:
#include " ServicesSoapBinding.nsmap "
int main( int argc, char ** argv) {
if ( argc != 4 && argc != 5 ) {
printf( " Usage: %s username password message [end_point]\n " , argv[ 0 ]);
exit( - 1 );
}
struct soap soap;
soap_init( & soap);
soap.userid = argv[ 1 ];
soap.passwd = argv[ 2 ];
struct _ns1__echo request;
struct _ns1__echoResponse response;
soap_ssl_init();
if ( soap_ssl_client_context( & soap, SOAP_SSL_NO_AUTHENTICATION, NULL, NULL, NULL, NULL, NULL) ) {
soap_print_fault( & soap, stderr);
exit( - 1 );
}
request.EchoMessage = argv[ 3 ];
char * endpoint = NULL;
if ( argc == 5 )
endpoint = argv[ 4 ];
printf( " username : %s\n " , soap.userid);
printf( " password : %s\n " , soap.passwd);
printf( " message : %s\n " , request.EchoMessage);
if ( endpoint )
printf( " end point : %s\n " , endpoint);
if ( soap_call___ns1__echo( & soap, endpoint, NULL, & request, & response) == SOAP_OK ) {
printf( " %s\n " , response.Echo);
}
else {
soap_print_fault( & soap, stderr);
}
soap_destroy( & soap);
soap_end( & soap);
soap_done( & soap);
return 0 ;
}
保存为echo.c,编译命令如下,注意增加了-DWITH_OPENSSL参数,以及需要链接libssl库。
gcc -DWITH_OPENSSL -O2 -o echo echo.c soapC.c soapClient.c ../../stdsoap2.c -I../.. -L../.. -lgsoap –lssl
服务端的编写相对麻烦,以下给出一个最简单的实现。与第二节的stock服务端程序相比,主要是增加了soap_ssl_server_context处理HTTPS协议,其中需要用到gsoap-2.7.17自带的ssl实例程序中的几个pem证书,把它们拷贝过来即可使用。另外,与不需要认证的应用相比,__ns1__echo增加了用户密码校验。这个案例里,设定客户端送上来的用户/密码应当为roy/liang,否则将返回401错误。
#include " soapH.h "
#include " ServicesSoapBinding.nsmap "
void * process_request( void * soap) {
pthread_detach(pthread_self());
if ( soap_ssl_accept(( struct soap * ) soap) != SOAP_OK )
soap_print_fault(( struct soap * ) soap, stderr);
else
soap_serve(( struct soap * ) soap);
soap_end(( struct soap * ) soap);
soap_free(( struct soap * ) soap);
return NULL;
}
int main( int argc, char ** argv) {
if ( argc != 2 ) {
printf( " Usage: %s port\n " , argv[ 0 ]);
exit( - 1 );
}
int port = atol(argv[ 1 ]);
pthread_t tid;
struct soap * tsoap;
struct soap soap;
soap_init( & soap);
soap_ssl_init();
if ( soap_ssl_server_context( & soap, SOAP_SSL_DEFAULT, " server.pem " , " password " , " cacert.pem " , NULL, " dh512.pem " , NULL, argv[ 0 ]) ) {
soap_print_fault( & soap, stderr);
exit( - 1 );
}
int m, s;
if ( (m = soap_bind( & soap, NULL, port, 100 )) < 0 ) {
soap_print_fault( & soap, stderr);
}
else {
printf( " Socket connect successfully: master socket = %d\n " , m);
int i = 0 ;
while ( 1 ) {
if ( (s = soap_accept( & soap)) < 0 ) {
soap_print_fault( & soap, stderr);
break ;
}
printf( " Connection %d accepted from IP = %d.%d.%d.%d, slave socket = %d\n " , ++ i, (soap.ip >> 24 ) & 0xff , (soap.ip >> 16 ) & 0xff , (soap.ip >> 8 ) & 0xff , soap.ip & 0xff , s);
tsoap = soap_copy( & soap);
if ( ! tsoap ) {
soap_closesock( & soap);
continue ;
}
pthread_create( & tid, NULL, & process_request, ( void * ) tsoap);
}
}
soap_done( & soap);
return 0 ;
}
int __ns1__echo(
struct soap * soap,
struct _ns1__echo * request,
struct _ns1__echoResponse * response) {
if ( ! soap -> userid || ! soap -> passwd || strcmp(soap -> userid, " roy " ) || strcmp(soap -> passwd, " liang " ) )
return 401 ;
int len = strlen(request -> EchoMessage);
response -> Echo = ( char * ) malloc( sizeof ( char ) * (len + 1 ));
strcpy(response -> Echo, request -> EchoMessage);
return SOAP_OK;
}
保存为echo_server.c,编译命令是:
gcc -DWITH_OPENSSL -O2 -o echo_server echo_server.c soapC.c soapServer.c ../../stdsoap2.c -I../.. -L../.. -lgsoap -lssl -lcrypto –lpthread
客户端和服务端都编译完成后,首先启动服务端:
-bash-3.2$ ./echo_server 6883
Socket connect successfully: master socket = 3
然后,在另一个窗口运行客户端,由于wsdl里已经指定默认end point是https://localhost:6883,因此,客户端并不需要额外给出。
正常的返回结果:
- -bash-3.2$ ./echo roy liang hi
- username : roy
- password : liang
- message : hi
- hi
用户、密码不正确将返回401错误:
- -bash-3.2$ ./echo roy xxx hi
- username : roy
- password : xxx
- message : hi
- Error 401 fault: SOAP-ENV:Server [no subcode]
- "HTTP/1.1 401 Unauthorized"
- Detail: <?xml version="1.0" encoding="UTF-8"?>
- <SOAP-ENV:Envelope xmlns:SOAP-ENV="http://schemas.xmlsoap.org/soap/envelope/" xmlns:SOAP-ENC="http://schemas.xmlsoap.org/soap/encoding/" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:ns1="http://echo.rsecure.com/ECHO"><SOAP-ENV:Body><SOAP-ENV:Fault><faultcode>SOAP-ENV:Client</faultcode><faultstring>HTTP Error: 401 Unauthorized</faultstring></SOAP-ENV:Fault></SOAP-ENV:Body></SOAP-ENV:Envelope>
基于HTTP的基本认证(Basic Authentication)比基于HTTPS的更加简单,在客户端和服务端的程序去除HTTPS处理即可,不再赘述。
另外,本案例中用到的证书的失效日期好像是2010年11月2日,在此之后执行的结果可能会不一样。
http://blog.csdn.net/yui/archive/2010/08/17/5817771.aspx