微信公众号搜"智元新知"关注
微信扫一扫可直接关注哦!

webservice设计


因此好的WebService接口,应该从下面几个方面仔细考虑:

一. 参数
(1) 参数应该直接使用简单的数据类型(POCO、POJO),甚至时间类型都可以考虑用string,只要双方约束好时间字符串的格式。
(2) 如果参数个数超过3个,那就需要考虑设计一个Class了,避免参数列表过长,当然这没有硬性规定。
(3) 设计统一的参数规则。比如对外提供的查询接口就要考虑分页相关的数据。保证类似的接口都有统一的参数定义,形成习惯是提升效率最好方式。
      业务参数和非业务参数应该分开,比如分页的数据就可以抽象出基类。


二. 异常
(1) 使用框架中定义的Exception类型,比如:SoapException,FaultException(WCF)。
(2) 尽量避免将异常定义在返回值中,通过返回值定义错误那么无论服务端还是客户端都要写很多if ... else 分支。
(3) 系统异常和业务异常要区分好,比如使用 SoapException 可以用 Code 来区分,比如:System.Error 表示系统错误,Bussiness.Error 表示业务错误
(4) 补充:.net framework  如果没有包装那么认有两种 fautCode:  soap:Client 和 soap:Server。假设客户端传入BadRequest 基本就是 soap:Client 错误,其他 没有自定义code的则就是 soap:Server 的错误

三. 安全
无论何时都要保证系统的安全性,我觉得安全也分系统安全和业务安全两种:
(1) 系统安全主要是指客户端的认证授权,调用次数(需要考虑会不会拖垮业务系统) 等
(2) 业务安全主要是指数据查询/操作权限,当然这个主要是从业务角度考虑的。


四. 日志
日志可以方便排查错误,还可以通过日志来分析服务基本信息(比如:调用次数,失败次数等),必要时还可以通过日志来进行重试。
另外要考虑开发的便捷,设计统一的日志拦截处理。

以 WebService Application (.NET 3.5) 为例,记录几种常用的编程技巧。
原始的 WebService 如下:

[csharp] @L_502_0@ copy print ?

在CODE上查看代码片

派生到我的代码片

  1. using System;  
  2. using System.Collections.Generic;  
  3. using System.Linq;  
  4. using System.Web;  
  5. using System.Web.Services;  
  6. using WebService1.Entity;  
  7. using WebService1.Service;  
  8. using System.Web.Services.Protocols;  
  9.   
  10. namespace WebService1  
  11. {  
  12.     [WebService(Namespace = "http://tempuri.org/")]  
  13.     [WebServiceBinding(ConformsTo = WsiProfiles.BasicProfile1_1)]  
  14.     [System.ComponentModel.ToolBoxItem(false)]  
  15.     public class Service1 : System.Web.Services.WebService  
  16.     {  
  17.         [WebMethod]         
  18.         public PageResult<Order> QueryOrder(Query<OrderCondition> queryInfo)  
  19.         {  
  20.             OrderService service = new OrderService();  
  21.             return service.Query(queryInfo);  
  22.         }  
  23.     }  
  24. }  

PageResult<T>,Query<T>  将统一的业务部分抽取出来,这样定义其他的业务对象就能简化了。

[html] @L_502_0@ copy print ?

在CODE上查看代码片

派生到我的代码片

  1. using System;  
  2. using System.Collections.Generic;  
  3.   
  4. namespace WebService1.Entity  
  5. {  
  6.     [Serializable]  
  7.     public class PageResult<T>  
  8.     {  
  9.         public int PageNo { get; set; }  
  10.         public int PageSize { get; set; }  
  11.         public int TotalCount { get; set; }  
  12.         public int PageCount { get; set; }  
  13.         public bool HasNextPage { get; set; }  
  14.         public List<T> Data { get; set; }  
  15.     }  
  16. }  
[csharp] @L_502_0@ copy print ?

在CODE上查看代码片

派生到我的代码片

  1. using System;  
  2. using System.Collections.Generic;  
  3.   
  4. namespace WebService1.Entity  
  5. {  
  6.     [Serializable]  
  7.     public class Query<T>  
  8.     {  
  9.         public int PageNo { getset; }  
  10.         public int PageSize { getset; }  
  11.         public T Condition { getset; }  
  12.     }  
  13. }  

跳过业务处理部分,来关注一下应用框架考虑的日志和安全拦截。可以利用 .NET framework 的 Soap Extensions ( msdn)  很容易地实现对 WebMethod 的 AOP。
Soap Extensions 可以通过两种方式“注入”: 自定义Atrribute 或者通过 Web.config 里的 soapExtensionTypes 进行声明。

TraceExtension 的实现:
[csharp] @L_502_0@ copy print ?

在CODE上查看代码片

派生到我的代码片

  1. using System;  
  2. using System.Collections.Generic;  
  3. using System.Linq;  
  4. using System.Web;  
  5. using System.IO;  
  6. using System.Web.Services.Protocols;  
  7. using log4net;  
  8. using System.Xml;  
  9.   
  10. namespace WebService1.Common  
  11. {  
  12.     public class TraceExtension : SoapExtension  
  13.     {  
  14.         private ILog logger = LogManager.GetLogger(typeof(TraceExtension));  
  15.   
  16.         Stream oldStream;  
  17.         Stream newStream;  
  18.           
  19.         public override System.IO.Stream ChainStream(System.IO.Stream stream)  
  20.         {  
  21.             oldStream = stream;  
  22.             newStream = new MemoryStream();  
  23.             return newStream;  
  24.         }  
  25.   
  26.         public override void ProcessMessage(SoapMessage message)  
  27.         {  
  28.             switch (message.Stage)  
  29.             {  
  30.                 case SoapMessageStage.BeforeDeserialize:  
  31.                       
  32.                     log4net.threadcontext.Properties["ip"] = HttpContext.Current.Request.UserHostAddress;  
  33.                     log4net.threadcontext.Properties["action"] = message.Action;  
  34.   
  35.                     WriteInput(message);  
  36.                     break;  
  37.                 case SoapMessageStage.AfterDeserialize:  
  38.                     break;  
  39.                 case SoapMessageStage.BeforeSerialize:  
  40.                     break;  
  41.                 case SoapMessageStage.AfterSerialize:  
  42.                     WriteOutput(message);  
  43.                     break;  
  44.                 default:  
  45.                     throw new Exception("Invalid Stage");  
  46.             }  
  47.         }  
  48.   
  49.         public override object Getinitializer(Type serviceType)  
  50.         {  
  51.             return null;  
  52.         }  
  53.   
  54.         public override object Getinitializer(LogicalMethodInfo methodInfo, SoapExtensionAttribute attr)  
  55.         {  
  56.             return null;  
  57.         }  
  58.   
  59.         public override void Initialize(object initializer)  
  60.         {  
  61.             //filename = (string)initializer;  
  62.         }  
  63.   
  64.         public void WriteOutput(SoapMessage message)  
  65.         {  
  66.             string soapString = (message is SoapServerMessage) ? "SoapResponse" : "SoapRequest";  
  67.             string content = GetContent(newStream);  
  68.             // 为了Format XML,如果从性能考虑应该去掉此处的处理  
  69.             if (!string.IsNullOrEmpty(content))  
  70.             {  
  71.                 XmlDocument xmlDoc = new XmlDocument();  
  72.                 xmlDoc.LoadXml(content);  
  73.                 using (StringWriter sw = new StringWriter())  
  74.                 {  
  75.                     using (XmlTextWriter xtw = new XmlTextWriter(sw))  
  76.                     {  
  77.                         xtw.Formatting = Formatting.Indented;  
  78.                         xmlDoc.Writeto(xtw);  
  79.                         content = sw.ToString();  
  80.                     }  
  81.                 }  
  82.             }  
  83.   
  84.             logger.Info(soapString + ":\n" + content);  
  85.   
  86.             copy(newStream, oldStream);  
  87.         }  
  88.   
  89.         public void WriteInput(SoapMessage message)  
  90.         {  
  91.             copy(oldStream, newStream);  
  92.   
  93.             string soapString = (message is SoapServerMessage) ? "SoapRequest" : "SoapResponse";  
  94.             string content = GetContent(newStream);  
  95.             logger.Info(soapString + ":\n" + content);  
  96.         }  
  97.   
  98.         void copy(Stream from, Stream to)  
  99.         {  
  100.             TextReader reader = new StreamReader(from);  
  101.             TextWriter writer = new StreamWriter(to);  
  102.             writer.WriteLine(reader.ReadToEnd());  
  103.             writer.Flush();  
  104.         }  
  105.   
  106.         string GetContent(Stream stream)  
  107.         {  
  108.             stream.Position = 0;  
  109.             TextReader reader = new StreamReader(stream);  
  110.             string content = reader.ReadToEnd();  
  111.             stream.Position = 0;  
  112.             return content;  
  113.         }  
  114.   
  115.     }  
  116.   
  117. }  
TraceAttribute 实现如下:
[csharp] @L_502_0@ copy print ?

在CODE上查看代码片

派生到我的代码片

  1. using System;  
  2. using System.Web.Services.Protocols;  
  3.   
  4. namespace WebService1.Common  
  5. {  
  6.     [AttributeUsage(AttributeTargets.Method)]  
  7.     public class TraceAttribute : SoapExtensionAttribute  
  8.     {  
  9.         private int priority = 0;  
  10.         public override Type ExtensionType  
  11.         {  
  12.             get { return typeof(TraceExtension); }  
  13.         }  
  14.   
  15.         public override int Priority  
  16.         {  
  17.             get { return priority; }  
  18.             set { priority = value; }  
  19.         }  
  20.     }  
  21. }  

其中 TraceExtension 利用 log4net 来记录调用 WebMethod 的Request 和 Response,还包括 ip 和 Action(Action其实对应的 WebMethod)
对应的 log4net 配置如下:
[html] @L_502_0@ copy print ?

在CODE上查看代码片

派生到我的代码片

  1. <log4net>  
  2.     <appender name="RollingFileAppender" type="log4net.Appender.RollingFileAppender">  
  3.         <param name="File" value="F:\Programming\VSProject2008\WebServiceSample\WebService1\WebService1\Logs\service.log"/>  
  4.                        <param name="DatePattern" value=".yyyy-MM-dd'.log'" />  
  5.         <param name="AppendToFile" value="true"/>  
  6.         <param name="MaxSizeRollBackups" value="10"/>  
  7.         <param name="MaximumFileSize" value="5MB"/>  
  8.         <param name="RollingStyle" value="Date"/>  
  9.         <param name="StaticLogFileName" value="false"/>  
  10.         <layout type="log4net.Layout.PatternLayout">  
  11.             <param name="ConversionPattern" value="%d [%t] %-5p [%property{ip}] [%property{action}] - %m%n"/>  
  12.         </layout>  
  13.     </appender>  
  14.     <root>  
  15.         <level value="DEBUG"/>  
  16.         <appender-ref ref="RollingFileAppender"/>  
  17.     </root>  
  18. </log4net>  

那么 WebMethod 只要加上 [Trace] 特性,就可以开启日志记录功能
[csharp] @L_502_0@ copy print ?

在CODE上查看代码片

派生到我的代码片

  1. [WebMethod]  
  2. [Trace]  
  3. public PageResult<Order> QueryOrder(Query<OrderCondition> queryInfo)  
  4. {  
  5.     OrderService service = new OrderService();  
  6.     return service.Query(queryInfo);  
  7. }  


输出日志如下:

[html] @L_502_0@ copy print ?

在CODE上查看代码片

派生到我的代码片

  1. 2014-05-25 22:05:02,292 [8] INFO  [127.0.0.1] [http://tempuri.org/QueryOrder] - SoapRequest:  
  2. <soapenv:Envelope xmlns:soapenv="http://schemas.xmlsoap.org/soap/envelope/" xmlns:tem="http://tempuri.org/">  
  3.    <soapenv:Body>  
  4.       <tem:QueryOrder>  
  5.          <!--Optional:-->  
  6.          <tem:queryInfo>  
  7.             <tem:PageNo>1</tem:PageNo>  
  8.             <tem:PageSize>1</tem:PageSize>  
  9.             <!--Optional:-->  
  10.             <tem:Condition>  
  11.                <!--Optional:-->  
  12.                <tem:StartTime>?</tem:StartTime>  
  13.                <!--Optional:-->  
  14.                <tem:EndTime>?</tem:EndTime>  
  15.                <!--Optional:-->  
  16.                <tem:ShopId>?</tem:ShopId>  
  17.                <!--Optional:-->  
  18.                <tem:ProductId>?</tem:ProductId>  
  19.             </tem:Condition>  
  20.          </tem:queryInfo>  
  21.       </tem:QueryOrder>  
  22.    </soapenv:Body>  
  23. </soapenv:Envelope>  
  24.   
  25. 2014-05-25 22:05:02,357 [8] INFO  [127.0.0.1] [http://tempuri.org/QueryOrder] - SoapResponse:  
  26. <?xml version="1.0" encoding="utf-8"?>  
  27. <soap:Envelope xmlns:soap="http://schemas.xmlsoap.org/soap/envelope/" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema">  
  28.   <soap:Body>  
  29.     <QueryOrderResponse xmlns="http://tempuri.org/">  
  30.       <QueryOrderResult>  
  31.         <PageNo>1</PageNo>  
  32.         <PageSize>1</PageSize>  
  33.         <TotalCount>3</TotalCount>  
  34.         <PageCount>1</PageCount>  
  35.         <HasNextPage>false</HasNextPage>  
  36.         <Data>  
  37.           <Order>  
  38.             <Id>1</Id>  
  39.             <OrderDate>2014-05-25 22:05:02</OrderDate>  
  40.             <ShopId>SHOP001</ShopId>  
  41.             <ProductId>PRD001</ProductId>  
  42.             <Quantity>1</Quantity>  
  43.             <Price>59</Price>  
  44.           </Order>  
  45.           ...  
  46.         </Data>  
  47.       </QueryOrderResult>  
  48.     </QueryOrderResponse>  
  49.   </soap:Body>  
  50. </soap:Envelope>  

接下来利用 SoapHeader 实现最基本的 Basic Authentication 校验,当然你不想每一个 WebMethod 去做相同的Check,同样我们实现一个 Soap Extension。

Authentication (SoapHeader) 的定义:
[csharp] @L_502_0@ copy print ?

在CODE上查看代码片

派生到我的代码片

  1. using System;  
  2. using System.Web.Services.Protocols;  
  3.   
  4. namespace WebService1.Common  
  5. {  
  6.     public class Authentication : SoapHeader  
  7.     {  
  8.         public string UserName { getset; }  
  9.         public string Password { getset; }  
  10.     }  
  11. }  
AuthCheckExtension 的实现:在 SoapMessage AfterDeserialize 这个阶段,取出客户端传的 SoapHeader 验证 UserName 和 Password 在服务端是否存在。
如果不存在或者错误则抛出 no auth ! 错误
[csharp] @L_502_0@ copy print ?

在CODE上查看代码片

派生到我的代码片

  1. using System;  
  2. using System.Collections.Generic;  
  3. using System.Linq;  
  4. using System.Web;  
  5. using System.IO;  
  6. using System.Web.Services.Protocols;  
  7. using WebService1.Config;  
  8.   
  9. namespace WebService1.Common  
  10. {  
  11.     public class AuthCheckExtension : SoapExtension  
  12.     {  
  13.         public override void ProcessMessage(SoapMessage message)  
  14.         {  
  15.             if (message.Stage == SoapMessageStage.AfterDeserialize)  
  16.             {  
  17.                 foreach (SoapHeader header in message.Headers)  
  18.                 {  
  19.                     if (header is Authentication)  
  20.                     {  
  21.                         var authHeader = header as Authentication;  
  22.                         var isValidUser = true;  
  23.                         var users = AuthConfiguration.AuthSettings.Users;  
  24.                         if (users != null && users.Count > 0)  
  25.                         {   
  26.                             isValidUser = users.Any(u => u.UserName == authHeader.UserName && u.Password == authHeader.Password);  
  27.                         }  
  28.   
  29.                         if (!isValidUser)  
  30.                             throw new BizException("no auth !");  
  31.                     }  
  32.                 }  
  33.             }  
  34.         }  
  35.   
  36.   
  37.         public override object Getinitializer(Type serviceType)  
  38.         {  
  39.             return null;  
  40.         }  
  41.   
  42.         public override object Getinitializer(LogicalMethodInfo methodInfo, SoapExtensionAttribute attribute)  
  43.         {  
  44.             return null;  
  45.         }  
  46.   
  47.         public override void Initialize(object initializer)  
  48.         {  
  49.             // 初始化 AuthSettings   
  50.             AuthConfiguration.Config();  
  51.         }  
  52.     }  
  53.   
  54. }  
然后给 WebMethod 加上 [SoapHeader("Authentication"),AuthCheck] 就OK了。
[csharp] @L_502_0@ copy print ?

在CODE上查看代码片

派生到我的代码片

  1. using System;  
  2. using System.Collections.Generic;  
  3. using System.Web;  
  4. using System.Web.Services;  
  5. using WebService1.Entity;  
  6. using WebService1.Service;  
  7. using System.Web.Services.Protocols;  
  8. using WebService1.Common;  
  9.   
  10. namespace WebService1  
  11. {  
  12.     [WebService(Namespace = "http://tempuri.org/")]  
  13.     [WebServiceBinding(ConformsTo = WsiProfiles.BasicProfile1_1)]  
  14.     [System.ComponentModel.ToolBoxItem(false)]  
  15.     public class Service1 : System.Web.Services.WebService  
  16.     {  
  17.         public Authentication Authentication { getset; }  
  18.   
  19.         [WebMethod]  
  20.         [Trace]  
  21.         [SoapHeader("Authentication"), AuthCheck]  
  22.         public PageResult<Order> QueryOrder(Query<OrderCondition> queryInfo)  
  23.         {  
  24.             OrderService service = new OrderService();  
  25.             return service.Query(queryInfo);  
  26.         }  
  27.   
  28.     }  
  29. }  

最后我们拿 SoapUI 来测试一下:





再来看看错误处理,如果故意输错 UserName:



顺便要赞一下 SoapUI,真是 WebService 调试的利器,还可以生成 .NET / Java 代码,推荐大家使用。我们用 SoapUI 生成一下 Java 代码
Java 客户端我决定用 CXF 来实现。所以要先配置一下 SoapUI:



JAVA CXF Client 代码

[java] @L_502_0@ copy print ?

在CODE上查看代码片

派生到我的代码片

  1. public static void main(String[] args) {  
  2.         try {  
  3.   
  4.             Service1 service1 = new Service1();  
  5.             Service1Soap service1Soap = service1.getService1Soap();  
  6.             BindingProvider provider = (BindingProvider)service1Soap;  
  7.   
  8.             List<Header> headers = new ArrayList<Header>();  
  9.             Authentication authentication = new Authentication();  
  10.             authentication.setUserName("fangxing");  
  11.             authentication.setPassword("123456");  
  12.             Header authHeader = new Header(ObjectFactory._Authentication_QNAME, authentication,  
  13.                     new JAXBDataBinding(Authentication.class));  
  14.   
  15.             headers.add(authHeader);  
  16.             provider.getRequestContext().put(Header.HEADER_LIST, headers);  
  17.   
  18.             QueryOfOrderCondition queryInfo = new QueryOfOrderCondition();  
  19.             queryInfo.setPageNo(1);  
  20.             queryInfo.setPageSize(1000);  
  21.   
  22.             OrderCondition condition = new OrderCondition();  
  23.             condition.setShopId("SHOP001");  
  24.             condition.setStartTime("2014-05-01 00:00:00");  
  25.             condition.setEndTime("2014-05-10 23:59:59");  
  26.   
  27.             queryInfo.setCondition(condition);  
  28.   
  29.             PageResultOfOrder result = service1Soap.queryOrder(queryInfo);  
  30.             System.out.println("get order size: " + result.getData().getorder().size());  
  31.   
  32.         } catch (Exception e) {  
  33.             e.printstacktrace();  
  34.         }  
  35.   
  36.     }  
示例代码下载,下载请阅 Readme.txt

版权声明:本文内容由互联网用户自发贡献,该文观点与技术仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 [email protected] 举报,一经查实,本站将立刻删除。

相关推荐