在我们当前的 SaaS 系统中 request 和 response 的验证包括两部分,一部分为 jaxb 的 schema 验证,一部分为局部业务规则约束的验证(通过在 service 实现中侵入代码进行验证)。
我一直觉得当前的验证(非数据库相关)不是很灵活,因此我就做了一个动态的验证框架,实现思路如下:
在我们的系统中每一个 service 对应多个 operation ,每一个 operation 既是一个 request/response 处理者。我们的验证是针对 request/response ,因此需要建表结构(示意)如下:
Vaidations
OperationID |
操作 id |
Type |
类型 request/response |
ValidateKey |
验证的 key ,即 request/response 的 xpath 路径 |
ValidateRule |
匹配 validateKey 的值 de 验证规则,利用正则表达式 |
所有的验证规则都会进行缓存以减少数据库操作。
在服务管理界面上可以动态的增加 / 删除 / 修改验证规则,在服务的调用时增加 SoapHandler ,执行过程如下:
1. 从 SOAPMessageContext 中获取请求 URL ,通过 URL 获取 OperationID 。
2. 根据 operationID 从缓存中获取请求或响应对应的所有验证规则。
3. 循环验证开始
4. 根据 ValidateKey 从 SOAPMessageContext 中获取请求 / 响应中对应的值。
5. 匹配 ValidateRule 对应的正则表达式,如果不匹配,则 fail-fast 直接抛出运行时异常;匹配则验证下一个。
6. 循环验证结束。
数据操作以及正则表达式验证大家可以自己实现,下面简单写了一个 handler ,只是为了验证通过 xpath 获取 soap request 中的 value ,如果有兴趣大家可以自己完善。
package com.hp.test.cxf.handlers; import java.io.ByteArrayInputStream; import java.io.ByteArrayOutputStream; import java.util.ArrayList; import java.util.HashMap; import java.util.Iterator; import java.util.List; import java.util.Map; import java.util.Set; import javax.xml.namespace.NamespaceContext; import javax.xml.namespace.QName; import javax.xml.soap.SOAPMessage; import javax.xml.ws.handler.MessageContext; import javax.xml.ws.handler.soap.SOAPHandler; import javax.xml.ws.handler.soap.SOAPMessageContext; import javax.xml.xpath.XPath; import javax.xml.xpath.XPathExpression; import javax.xml.xpath.XPathFactory; import org.xml.sax.InputSource; public class ValidationHandler implements SOAPHandler<SOAPMessageContext> { public Set<QName> getHeaders() { // TODO Auto-generated method stub return null; } public void close(MessageContext arg0) { // TODO Auto-generated method stub } public boolean handleFault(SOAPMessageContext arg0) { // TODO Auto-generated method stub return false; } public boolean handleMessage(SOAPMessageContext smc) { Boolean outboundProperty = (Boolean)smc.get(MessageContext.MESSAGE_OUTBOUND_PROPERTY); if (outboundProperty.booleanValue()) { //System.out.println("Out:"+smc.toString()); return true; } else { try{ //从缓存中获取此URL(operation)对应的所有验证规则 //循环验证key-rule SimpleNamespaceContext nsContext = new SimpleNamespaceContext(); nsContext.setNamespace("soapenv", "http://schemas.xmlsoap.org/soap/envelope/"); // instead of "SOAP-ENV" nsContext.setNamespace("cxf", "http://cxf.test.hp.com/"); String name = getDataXpath(smc.getMessage(), "/soapenv:Envelope/soapenv:Body/cxf:Person/name/child::text()", nsContext); //if(!name.matchs(rule)){throw new RuntimeException("name is invalid!");} //循环结束 }catch(Exception e){e.printStackTrace();} return true; } } public String getDataXpath( SOAPMessage message, String expression, NamespaceContext nsContext ) throws java.io.IOException, javax.xml.soap.SOAPException, javax.xml.xpath.XPathExpressionException { ByteArrayOutputStream out = new ByteArrayOutputStream(); message.writeTo(out); InputSource inputSource = new InputSource( new ByteArrayInputStream(out.toByteArray()) ); XPathFactory factory = XPathFactory.newInstance(); XPath xPath = factory.newXPath(); if (nsContext != null) xPath.setNamespaceContext( nsContext ); XPathExpression xpathExpression = xPath.compile( expression ); return xpathExpression.evaluate( inputSource ); } } class SimpleNamespaceContext implements NamespaceContext { private Map<String,String> map; public SimpleNamespaceContext() { map = new HashMap<String,String>(); } public void setNamespace( String prefix, String namespaceURI ){ map.put(prefix, namespaceURI); } // !!! doesn't fully implement getNamespaceURI API spec !!! public String getNamespaceURI( String prefix ){ return map.get(prefix); } // !!! doesn't fully implement getPrefix API spec !!! public String getPrefix( String namespaceURI ){ SinglePrefixCollector collector = new SinglePrefixCollector(); collectPrefixes( namespaceURI, collector ); return collector.getResult(); } // !!! doesn't fully implement getPrefixes API spec !!! public Iterator getPrefixes( String namespaceURI ){ MultiPrefixCollector collector = new MultiPrefixCollector(); collectPrefixes( namespaceURI, collector ); return collector.getResult(); } protected void collectPrefixes( String namespaceURI, PrefixCollector collector ) { Iterator<String> iterator = map.keySet().iterator(); while( iterator.hasNext() ) { String prefix = iterator.next(); if ( getNamespaceURI(prefix).equals(namespaceURI) ) { boolean addMore = collector.addPrefix(prefix); if(! addMore ) break; } } } protected interface PrefixCollector { // template method for subclasses // return: true - continue collecting; false - stop collecting. boolean addPrefix(String prefix); } static protected class SinglePrefixCollector implements PrefixCollector { private String prefix = null; // returns false to stop further additions as it can only hold one prefix public boolean addPrefix( String prefix ) { this.prefix = prefix; return false; } public String getResult(){ return prefix; } } static protected class MultiPrefixCollector implements PrefixCollector { private List<String> prefixes = new ArrayList<String>(); // returns true as it can hold more than one prefix public boolean addPrefix( String prefix ) { prefixes.add(prefix); return true; } public Iterator getResult() { return prefixes.iterator(); } } }
请求的 wsdl 如下
<soapenv:Envelope xmlns:soapenv="http://schemas.xmlsoap.org/soap/envelope/" xmlns:cxf="http://cxf.test.hp.com/"> <soapenv:Header/> <soapenv:Body> <cxf:Person> <name>1</name> <address>2</address> <phone>3</phone> <email>eddis</email> <addresslist> <address> <address>8</address> <test>9</test> </address> </addresslist> </cxf:Person> </soapenv:Body> </soapenv:Envelope>