一. 参数
(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 如下:
- using System;
- using System.Collections.Generic;
- using System.Linq;
- using System.Web;
- using System.Web.Services;
- using WebService1.Entity;
- using WebService1.Service;
- using System.Web.Services.Protocols;
- namespace WebService1
- {
- [WebService(Namespace = "http://tempuri.org/")]
- [WebServiceBinding(ConformsTo = WsiProfiles.BasicProfile1_1)]
- [System.ComponentModel.ToolBoxItem(false)]
- public class Service1 : System.Web.Services.WebService
- {
- [WebMethod]
- public PageResult<Order> QueryOrder(Query<OrderCondition> queryInfo)
- {
- OrderService service = new OrderService();
- return service.Query(queryInfo);
- }
- }
- }
PageResult<T>,Query<T> 将统一的业务部分抽取出来,这样定义其他的业务对象就能简化了。
- using System;
- using System.Collections.Generic;
- namespace WebService1.Entity
- {
- [Serializable]
- public class PageResult<T>
- {
- public int PageNo { get; set; }
- public int PageSize { get; set; }
- public int TotalCount { get; set; }
- public int PageCount { get; set; }
- public bool HasNextPage { get; set; }
- public List<T> Data { get; set; }
- }
- }
跳过业务处理部分,来关注一下应用框架考虑的日志和安全拦截。可以利用 .NET framework 的 Soap Extensions ( msdn) 很容易地实现对 WebMethod 的 AOP。
Soap Extensions 可以通过两种方式“注入”: 自定义Atrribute 或者通过 Web.config 里的 soapExtensionTypes 进行声明。
TraceExtension 的实现:
- using System;
- using System.Collections.Generic;
- using System.Linq;
- using System.Web;
- using System.IO;
- using System.Web.Services.Protocols;
- using log4net;
- using System.Xml;
- namespace WebService1.Common
- {
- public class TraceExtension : SoapExtension
- {
- private ILog logger = LogManager.GetLogger(typeof(TraceExtension));
- Stream oldStream;
- Stream newStream;
- public override System.IO.Stream ChainStream(System.IO.Stream stream)
- {
- oldStream = stream;
- newStream = new MemoryStream();
- return newStream;
- }
- public override void ProcessMessage(SoapMessage message)
- {
- switch (message.Stage)
- {
- case SoapMessageStage.BeforeDeserialize:
- log4net.threadcontext.Properties["ip"] = HttpContext.Current.Request.UserHostAddress;
- log4net.threadcontext.Properties["action"] = message.Action;
- WriteInput(message);
- break;
- case SoapMessageStage.AfterDeserialize:
- break;
- case SoapMessageStage.BeforeSerialize:
- break;
- case SoapMessageStage.AfterSerialize:
- WriteOutput(message);
- break;
- default:
- throw new Exception("Invalid Stage");
- }
- }
- public override object Getinitializer(Type serviceType)
- {
- return null;
- }
- public override object Getinitializer(LogicalMethodInfo methodInfo, SoapExtensionAttribute attr)
- {
- return null;
- }
- public override void Initialize(object initializer)
- {
- //filename = (string)initializer;
- }
- public void WriteOutput(SoapMessage message)
- {
- string soapString = (message is SoapServerMessage) ? "SoapResponse" : "SoapRequest";
- string content = GetContent(newStream);
- // 为了Format XML,如果从性能考虑应该去掉此处的处理
- if (!string.IsNullOrEmpty(content))
- {
- XmlDocument xmlDoc = new XmlDocument();
- xmlDoc.LoadXml(content);
- using (StringWriter sw = new StringWriter())
- {
- using (XmlTextWriter xtw = new XmlTextWriter(sw))
- {
- xtw.Formatting = Formatting.Indented;
- xmlDoc.Writeto(xtw);
- content = sw.ToString();
- }
- }
- }
- logger.Info(soapString + ":\n" + content);
- copy(newStream, oldStream);
- }
- public void WriteInput(SoapMessage message)
- {
- copy(oldStream, newStream);
- string soapString = (message is SoapServerMessage) ? "SoapRequest" : "SoapResponse";
- string content = GetContent(newStream);
- logger.Info(soapString + ":\n" + content);
- }
- void copy(Stream from, Stream to)
- {
- TextReader reader = new StreamReader(from);
- TextWriter writer = new StreamWriter(to);
- writer.WriteLine(reader.ReadToEnd());
- writer.Flush();
- }
- string GetContent(Stream stream)
- {
- stream.Position = 0;
- TextReader reader = new StreamReader(stream);
- string content = reader.ReadToEnd();
- stream.Position = 0;
- return content;
- }
- }
- }
- using System;
- using System.Web.Services.Protocols;
- namespace WebService1.Common
- {
- [AttributeUsage(AttributeTargets.Method)]
- public class TraceAttribute : SoapExtensionAttribute
- {
- private int priority = 0;
- public override Type ExtensionType
- {
- get { return typeof(TraceExtension); }
- }
- public override int Priority
- {
- get { return priority; }
- set { priority = value; }
- }
- }
- }
其中 TraceExtension 利用 log4net 来记录调用 WebMethod 的Request 和 Response,还包括 ip 和 Action(Action其实对应的 WebMethod)
对应的 log4net 配置如下:
- <log4net>
- <appender name="RollingFileAppender" type="log4net.Appender.RollingFileAppender">
- <param name="File" value="F:\Programming\VSProject2008\WebServiceSample\WebService1\WebService1\Logs\service.log"/>
- <param name="DatePattern" value=".yyyy-MM-dd'.log'" />
- <param name="AppendToFile" value="true"/>
- <param name="MaxSizeRollBackups" value="10"/>
- <param name="MaximumFileSize" value="5MB"/>
- <param name="RollingStyle" value="Date"/>
- <param name="StaticLogFileName" value="false"/>
- <layout type="log4net.Layout.PatternLayout">
- <param name="ConversionPattern" value="%d [%t] %-5p [%property{ip}] [%property{action}] - %m%n"/>
- </layout>
- </appender>
- <root>
- <level value="DEBUG"/>
- <appender-ref ref="RollingFileAppender"/>
- </root>
- </log4net>
那么 WebMethod 只要加上 [Trace] 特性,就可以开启日志记录功能。
- [WebMethod]
- [Trace]
- public PageResult<Order> QueryOrder(Query<OrderCondition> queryInfo)
- {
- OrderService service = new OrderService();
- return service.Query(queryInfo);
- }
输出日志如下:
- 2014-05-25 22:05:02,292 [8] INFO [127.0.0.1] [http://tempuri.org/QueryOrder] - SoapRequest:
- <soapenv:Envelope xmlns:soapenv="http://schemas.xmlsoap.org/soap/envelope/" xmlns:tem="http://tempuri.org/">
- <soapenv:Body>
- <tem:QueryOrder>
- <!--Optional:-->
- <tem:queryInfo>
- <tem:PageNo>1</tem:PageNo>
- <tem:PageSize>1</tem:PageSize>
- <!--Optional:-->
- <tem:Condition>
- <!--Optional:-->
- <tem:StartTime>?</tem:StartTime>
- <!--Optional:-->
- <tem:EndTime>?</tem:EndTime>
- <!--Optional:-->
- <tem:ShopId>?</tem:ShopId>
- <!--Optional:-->
- <tem:ProductId>?</tem:ProductId>
- </tem:Condition>
- </tem:queryInfo>
- </tem:QueryOrder>
- </soapenv:Body>
- </soapenv:Envelope>
- 2014-05-25 22:05:02,357 [8] INFO [127.0.0.1] [http://tempuri.org/QueryOrder] - SoapResponse:
- <?xml version="1.0" encoding="utf-8"?>
- <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">
- <soap:Body>
- <QueryOrderResponse xmlns="http://tempuri.org/">
- <QueryOrderResult>
- <PageNo>1</PageNo>
- <PageSize>1</PageSize>
- <TotalCount>3</TotalCount>
- <PageCount>1</PageCount>
- <HasNextPage>false</HasNextPage>
- <Data>
- <Order>
- <Id>1</Id>
- <OrderDate>2014-05-25 22:05:02</OrderDate>
- <ShopId>SHOP001</ShopId>
- <ProductId>PRD001</ProductId>
- <Quantity>1</Quantity>
- <Price>59</Price>
- </Order>
- ...
- </Data>
- </QueryOrderResult>
- </QueryOrderResponse>
- </soap:Body>
- </soap:Envelope>
接下来利用 SoapHeader 实现最基本的 Basic Authentication 校验,当然你不想每一个 WebMethod 去做相同的Check,同样我们实现一个 Soap Extension。
Authentication (SoapHeader) 的定义:
如果不存在或者错误则抛出 no auth ! 的错误。
- using System;
- using System.Collections.Generic;
- using System.Linq;
- using System.Web;
- using System.IO;
- using System.Web.Services.Protocols;
- using WebService1.Config;
- namespace WebService1.Common
- {
- public class AuthCheckExtension : SoapExtension
- {
- public override void ProcessMessage(SoapMessage message)
- {
- if (message.Stage == SoapMessageStage.AfterDeserialize)
- {
- foreach (SoapHeader header in message.Headers)
- {
- if (header is Authentication)
- {
- var authHeader = header as Authentication;
- var isValidUser = true;
- var users = AuthConfiguration.AuthSettings.Users;
- if (users != null && users.Count > 0)
- {
- isValidUser = users.Any(u => u.UserName == authHeader.UserName && u.Password == authHeader.Password);
- }
- if (!isValidUser)
- throw new BizException("no auth !");
- }
- }
- }
- }
- public override object Getinitializer(Type serviceType)
- {
- return null;
- }
- public override object Getinitializer(LogicalMethodInfo methodInfo, SoapExtensionAttribute attribute)
- {
- return null;
- }
- public override void Initialize(object initializer)
- {
- // 初始化 AuthSettings
- AuthConfiguration.Config();
- }
- }
- }
- using System;
- using System.Collections.Generic;
- using System.Web;
- using System.Web.Services;
- using WebService1.Entity;
- using WebService1.Service;
- using System.Web.Services.Protocols;
- using WebService1.Common;
- namespace WebService1
- {
- [WebService(Namespace = "http://tempuri.org/")]
- [WebServiceBinding(ConformsTo = WsiProfiles.BasicProfile1_1)]
- [System.ComponentModel.ToolBoxItem(false)]
- public class Service1 : System.Web.Services.WebService
- {
- public Authentication Authentication { get; set; }
- [WebMethod]
- [Trace]
- [SoapHeader("Authentication"), AuthCheck]
- public PageResult<Order> QueryOrder(Query<OrderCondition> queryInfo)
- {
- OrderService service = new OrderService();
- return service.Query(queryInfo);
- }
- }
- }
最后我们拿 SoapUI 来测试一下:
再来看看错误处理,如果故意输错 UserName:
顺便要赞一下 SoapUI,真是 WebService 调试的利器,还可以生成 .NET / Java 代码,推荐大家使用。我们用 SoapUI 生成一下 Java 代码。
Java 客户端我决定用 CXF 来实现。所以要先配置一下 SoapUI:
JAVA CXF Client 代码:
- public static void main(String[] args) {
- try {
- Service1 service1 = new Service1();
- Service1Soap service1Soap = service1.getService1Soap();
- BindingProvider provider = (BindingProvider)service1Soap;
- List<Header> headers = new ArrayList<Header>();
- Authentication authentication = new Authentication();
- authentication.setUserName("fangxing");
- authentication.setPassword("123456");
- Header authHeader = new Header(ObjectFactory._Authentication_QNAME, authentication,
- new JAXBDataBinding(Authentication.class));
- headers.add(authHeader);
- provider.getRequestContext().put(Header.HEADER_LIST, headers);
- QueryOfOrderCondition queryInfo = new QueryOfOrderCondition();
- queryInfo.setPageNo(1);
- queryInfo.setPageSize(1000);
- OrderCondition condition = new OrderCondition();
- condition.setShopId("SHOP001");
- condition.setStartTime("2014-05-01 00:00:00");
- condition.setEndTime("2014-05-10 23:59:59");
- queryInfo.setCondition(condition);
- PageResultOfOrder result = service1Soap.queryOrder(queryInfo);
- System.out.println("get order size: " + result.getData().getorder().size());
- } catch (Exception e) {
- e.printstacktrace();
- }
- }
版权声明:本文内容由互联网用户自发贡献,该文观点与技术仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 [email protected] 举报,一经查实,本站将立刻删除。