技术开发 频道

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描述如下:

view plaincopy to clipboardprint?
<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。声明如下:

 

view plaincopy to clipboardprint?

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的实现:

 

view plaincopy to clipboardprint?

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的结果也要自己去解析!!

 

view plaincopy to clipboardprint?

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中处理结果。

 

view plaincopy to clipboardprint?

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中完成:

 

view plaincopy to clipboardprint?

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的回调处理:

 

view plaincopy to clipboardprint?

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去处理下级节点,就是说以下的 XML内容由CHelloWorldResult类的负责处理了。

继续,看看如何取出结果值,就是<HelloWorldResult>Hello World</HelloWorldResult>中的字串HelloWorld,这个代码在CHelloWorldResult中:

view plaincopy to clipboardprint?

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框架。

0
相关文章