Symbian学习笔记(17)
【IT168 技术文档】如何使用Symbian中提供的WebService框架来SayHello。
从SDK文档中提供的资料来看这个接口似乎有点复杂,包括了Connection API、Description API和Manager API三套东西,此外还涉到了XML的解析之类的一些API的应用。
阅读了一下它的例子程序(S60Ex目录下的AddressBook),让我更晕乎了。怎么跟自己平时使用的WebService不一样了?
在SDK文档中关于CSenServiceConnection有这么一段描述:
Web Services包括两种不同的框架模型:
1. Identity Based Web Services Framework (ID-WSF). The framework ID for this is KDefaultIdWsfFrameworkID ("ID-WSF").
2. Basic Web Services Framework. Framework ID is KDefaultBasicWebServicesFrameworkID ("WS-I").
如果提供了Contract则缺省使用ID-WSF。
首先用.NET做一个简单的WebServices来测试,就用缺省产生的HelloWorld吧。很简单的,它的SOAP描述如下:
<PRE class=csharp name="code">POST /uim/PService.asmx HTTP/1.1
Host: localhost
Content-Type: text/xml; charset=utf-8
Content-Length: length
SOAPAction: "urn:pservice:helloworld"
<?xml version="1.0" encoding="utf-8"?>
<soap:Envelope xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:soap="http://schemas.xmlsoap.org/soap/envelope/">
<soap:Body>
<HelloWorld xmlns="http://sharetop/pservice" />
</soap:Body>
</soap:Envelope>
HTTP/1.1 200 OK
Content-Type: text/xml; charset=utf-8
Content-Length: length
<?xml version="1.0" encoding="utf-8"?>
<soap:Envelope xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:soap="http://schemas.xmlsoap.org/soap/envelope/">
<soap:Body>
<HelloWorldResponse xmlns="http://sharetop/pservice">
<HelloWorldResult>string</HelloWorldResult>
</HelloWorldResponse>
</soap:Body>
</soap:Envelope></PRE>
view plaincopy to clipboardprint?
POST /uim/PService.asmx HTTP/1.1
Host: localhost
Content-Type: text/xml; charset=utf-8
Content-Length: length
SOAPAction: "urn:pservice:helloworld"
<?xml version="1.0" encoding="utf-8"?>
<soap:Envelope xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:soap="http://schemas.xmlsoap.org/soap/envelope/">
<soap:Body>
<HelloWorld xmlns="http://sharetop/pservice" />
</soap:Body>
</soap:Envelope>
HTTP/1.1 200 OK
Content-Type: text/xml; charset=utf-8
Content-Length: length
<?xml version="1.0" encoding="utf-8"?>
<soap:Envelope xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:soap="http://schemas.xmlsoap.org/soap/envelope/">
<soap:Body>
<HelloWorldResponse xmlns="http://sharetop/pservice">
<HelloWorldResult>string</HelloWorldResult>
</HelloWorldResponse>
</soap:Body>
</soap:Envelope>
POST /uim/PService.asmx HTTP/1.1
Host: localhost
Content-Type: text/xml; charset=utf-8
Content-Length: length
SOAPAction: "urn:pservice:helloworld"
<?xml version="1.0" encoding="utf-8"?>
<soap:Envelope xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:soap="http://schemas.xmlsoap.org/soap/envelope/">
<soap:Body>
<HelloWorld xmlns="http://sharetop/pservice" />
</soap:Body>
</soap:Envelope>
HTTP/1.1 200 OK
Content-Type: text/xml; charset=utf-8
Content-Length: length
<?xml version="1.0" encoding="utf-8"?>
<soap:Envelope xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:soap="http://schemas.xmlsoap.org/soap/envelope/">
<soap:Body>
<HelloWorldResponse xmlns="http://sharetop/pservice">
<HelloWorldResult>string</HelloWorldResult>
</HelloWorldResponse>
</soap:Body>
</soap:Envelope>
下面我们自己来做一个WS的客户端实例吧。先用向导生成一个HelloWorld应用,为了研究方便,我们不打算做什么界面,所有的输出都通过LOG输出到日志文件。
为了编码方便,我们增加一个类WebEngine,它应该派生于CSenBaseFragment和MSenServiceConsumer。声明如下:
class CWebEngine : public CSenBaseFragment, public MSenServiceConsumer
{
public:
~CWebEngine();
static CWebEngine* NewL();
static CWebEngine* NewLC();
void ConnectL();
void SayHello();
//from MSenServiceConsumer
virtual void HandleMessageL(const TDesC8& aMessage);
virtual void HandleErrorL(const TInt aErrorCode,const TDesC8& aError);
virtual void SetStatus(const TInt aStatus);
protected:
//from CSenBaseFragment
virtual void StartElementL(const TDesC8& aNsUri, const TDesC8& aLocalName, const TDesC8& aQName, const Xml::RAttributeArray& aAttrs);
virtual void EndElementL(const TDesC8& aNsUri, const TDesC8& aLocalName, const TDesC8& aQName);
private:
CWebEngine();
void ConstructL();
public:
CHelloWorldResult * delegate;
private:
CSenServiceConnection* iConnection;
CSenXmlServiceDescription* iSession;
CSenXmlReader* iXmlReader;
};
class CWebEngine : public CSenBaseFragment, public MSenServiceConsumer
{
public:
~CWebEngine();
static CWebEngine* NewL();
static CWebEngine* NewLC();
void ConnectL();
void SayHello();
//from MSenServiceConsumer
virtual void HandleMessageL(const TDesC8& aMessage);
virtual void HandleErrorL(const TInt aErrorCode,const TDesC8& aError);
virtual void SetStatus(const TInt aStatus);
protected:
//from CSenBaseFragment
virtual void StartElementL(const TDesC8& aNsUri, const TDesC8& aLocalName, const TDesC8& aQName, const Xml::RAttributeArray& aAttrs);
virtual void EndElementL(const TDesC8& aNsUri, const TDesC8& aLocalName, const TDesC8& aQName);
private:
CWebEngine();
void ConstructL();
public:
CHelloWorldResult * delegate;
private:
CSenServiceConnection* iConnection;
CSenXmlServiceDescription* iSession;
CSenXmlReader* iXmlReader;
};
除了实现两个父类的方法以外,还要增加ConnectL()用来连接,SayHello()用来调用远程方法。那个delegate是一个 CHelloWorldResult类的实例,这个类同样派生于CSenDomFragment,说明它对应一段XML内容,我们用它来处理结果,就是那个HelloWorldResponse标签下的内容。
这个WebEngine的实现逻辑是:先在ConnectL中初始化WS客户端,在SetStatus回调中取当前状态值如果为 KSenConnectionStatusReady ,则可以调用SayHello去执行那个WS的方法,然后,在HandleMessageL回调中将得到的结果(XML内容的字节流)去解析一下,解析 XML的回调就是那两个StartElement和EndElement。
继续刚才的,现在来看具体代码,先是ConnectL的实现:
void CWebEngine::ConnectL()
{
CSenXmlServiceDescription* pattern = CSenXmlServiceDescription::NewLC();
pattern->SetFrameworkIdL(KDefaultBasicWebServicesFrameworkID);
pattern->SetEndPointL(KWSEndPoint);
delete iConnection;
iConnection = NULL;
iConnection = CSenServiceConnection::NewL(*this, *pattern);
CleanupStack::PopAndDestroy(pattern);
}
void CWebEngine::ConnectL()
{
CSenXmlServiceDescription* pattern = CSenXmlServiceDescription::NewLC();
pattern->SetFrameworkIdL(KDefaultBasicWebServicesFrameworkID);
pattern->SetEndPointL(KWSEndPoint);
delete iConnection;
iConnection = NULL;
iConnection = CSenServiceConnection::NewL(*this, *pattern);
CleanupStack::PopAndDestroy(pattern);
}
这里注意一点与那个AddressBook例子不同的是我们声明了不同框架类型是 KDefaultBasicWebServicesFrameworkID,并且这样只需要提供EndPoint而不需要Contract了。 KWSEndPoint的值是在CPP前声明了:_LIT8(KWSEndPoint,"http://192.168.0.201/uim /PService.asmx");
CSenServiceConnection::NewL的两个参数,一是自己(即MSenServiceConsumer)负责处理回调,二是一个CSenXmlServiceDescription负责参数配置。
在回调SetStatus中我只是简单地打印出状态值。
再看那个SayHello的实现吧,在这个函数中要负责封装SOAP消息包,这时我才遇到了使用Symbian的WebServiceAPI烦人的问题:原来这个SOAP包要自己封装啊!同样SOAP的结果也要自己去解析!!
void CWebEngine::SayHello()
{
if(iConnectionState==1){
//send
CSenSoapEnvelope *env = CSenSoapEnvelope::NewL();
CleanupStack::PushL(env);
env->SetSoapActionL(KWSContract);
env->BodyL().AddElementL(KWSNamespace,KWSHelloworld);
iConnection->SendL(*env);
CleanupStack::PopAndDestroy(env);
}
}
void CWebEngine::SayHello()
{
if(iConnectionState==1){
//send
CSenSoapEnvelope *env = CSenSoapEnvelope::NewL();
CleanupStack::PushL(env);
env->SetSoapActionL(KWSContract);
env->BodyL().AddElementL(KWSNamespace,KWSHelloworld);
iConnection->SendL(*env);
CleanupStack::PopAndDestroy(env);
}
}
好在HelloWorld不需要参数,所以这个SOAP请求还算简单,注意这个SetSoapActionL函数它的KWSContract就是那个"urn:pservice:helloworld" (见上篇中的SOAP请求描述)。因为CSenSoapEnvelope同样派生于CSenBaseFragment ,所以它的Body也可以增加下级节点,上面的代码很好理解。
一旦调用了iConnection->SendL以后,手机会弹出选择接入点,说明这里开始连接网络了,得到结果后,我们回调HandleMessageL中处理结果。
void CWebEngine::HandleMessageL(const TDesC8& aMessage)
{
RDebug::Printf("===================HandleMessageL");
LOG_ALL(aMessage);
SetReader(*iXmlReader);
ParseL(aMessage);
}
void CWebEngine::HandleMessageL(const TDesC8& aMessage)
{
RDebug::Printf("===================HandleMessageL");
LOG_ALL(aMessage);
SetReader(*iXmlReader);
ParseL(aMessage);
}
这里我们将得到的结果(完整的SOAP响应的XML内容)交给iXmlReader去解析,于是此时又会涉及到另两个回调StartElement和EndElement。注意这里补充一下iXmlReader的初始化在WebEngine的ConstructL中完成:
void CWebEngine::ConstructL()
{
LOG_OPEN();
CSenBaseFragment::BaseConstructL(KQueryResponseLocalName);
iXmlReader = CSenXmlReader::NewL();
}
void CWebEngine::ConstructL()
{
LOG_OPEN();
CSenBaseFragment::BaseConstructL(KQueryResponseLocalName);
iXmlReader = CSenXmlReader::NewL();
}
两句话:一是因为自己是派生于CSenBaseFragment,所以先调用BaseConstructL构造一下自己是一个HelloWorldResponse标签的XML节点。二是构造出iXmlReader实例。
下面继续说解析XML的回调处理:
void CWebEngine::StartElementL(const TDesC8& aNsUri, const TDesC8& aLocalName, const TDesC8& aQName, const Xml::RAttributeArray& aAttrs)
{
RDebug::Printf("================StartElement");
_LIT(KFmt,"StartElement (%s)");
LOG_FORMAT((KFmt,aLocalName));
if(aLocalName==KQueryResponseLocalName){
delegate = CHelloWorldResult::NewL(aNsUri,aLocalName,aQName);
CleanupStack::PushL(delegate);
DelegateParsingL(*delegate);
CleanupStack::Pop(delegate);
}
}
void CWebEngine::EndElementL(const TDesC8& aNsUri, const TDesC8& aLocalName, const TDesC8& aQName)
{
RDebug::Printf("==================EndElement");
_LIT(KFmt,"EndElement (%s)");
LOG_FORMAT((KFmt,aLocalName));
CSenBaseFragment::EndElementL(aNsUri, aLocalName, aQName);
}
void CWebEngine::StartElementL(const TDesC8& aNsUri, const TDesC8& aLocalName, const TDesC8& aQName, const Xml::RAttributeArray& aAttrs)
{
RDebug::Printf("================StartElement");
_LIT(KFmt,"StartElement (%s)");
LOG_FORMAT((KFmt,aLocalName));
if(aLocalName==KQueryResponseLocalName){
delegate = CHelloWorldResult::NewL(aNsUri,aLocalName,aQName);
CleanupStack::PushL(delegate);
DelegateParsingL(*delegate);
CleanupStack::Pop(delegate);
}
}
void CWebEngine::EndElementL(const TDesC8& aNsUri, const TDesC8& aLocalName, const TDesC8& aQName)
{
RDebug::Printf("==================EndElement");
_LIT(KFmt,"EndElement (%s)");
LOG_FORMAT((KFmt,aLocalName));
CSenBaseFragment::EndElementL(aNsUri, aLocalName, aQName);
}
这个EndElement没啥好说的,就是调一下老子的EndElement罢了。倒是那个StartElement函数它在遇到 HelloWorldResponse标签时会交给delegate去处理下级节点,就是说
继续,看看如何取出结果值,就是<HelloWorldResult>Hello World</HelloWorldResult>中的字串HelloWorld,这个代码在CHelloWorldResult中:
TPtrC8 CHelloWorldResult::Result()
{
CSenElement * pElement = AsElement().Element(KHelloResult);
if(pElement)
{
_LIT(STR,"result----");
LOG(STR);
return pElement->Content();
}
else {
LOG(_L("no result"));
return KNullDesC8();
}
}
TPtrC8 CHelloWorldResult::Result()
{
CSenElement * pElement = AsElement().Element(KHelloResult);
if(pElement)
{
_LIT(STR,"result----");
LOG(STR);
return pElement->Content();
}
else {
LOG(_L("no result"));
return KNullDesC8();
}
}
其实这儿也好理解,看看CSenElement的SDK文档就知道个八九不离十了。
总结一下:
1.利用Symbian中的Web Services API实现一个WEB服务客户端是比较烦人的事情,主要的麻烦点在于SOAP请求与响应的XML内容都得自己去构造和解析。
2.常用的WebService只有EndPoint没有ID服务,所以它应该是基础型的WS框架。