在我们当前的 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>