最近玩了玩 Google 云,上网找了下如何在 Google 云平台 上部署自己的 WebService,还是没有找到中文资料,不过找到了两份比较好的英文资料:
http://googcloudlabs.appspot.com/codelabexercise5.html
https://developers.google.com/appengine/articles/soap?hl=en
按照里边的例子,把前一阵 防XSS跨站脚本攻击的 WebService 部署到了 Goolge Appengine 云平台上,分享一下经验
原 Tomcat 版 Xss 过滤:http://blog.csdn.net/lxfan/article/details/8162257
部署到 Goolge Appengine 上的 Xss 过滤 WebService 地址:http://xssfilter.wegabrow.com/
注:这里我做了一个域名的映射,具体怎么将域名映射到 Google Appengine,大家可以 Google 一下。
下边说一下如何在 Google AppEngine (以下简称GAE)实现WebService。主要是使用 javax.xml.soap 和 JAX-B 来进行 SOAP 交互。
以下是将 AntiSamy Xss Filter 封装成 GAE WebService的具体步骤(开发环境 eclipse):
1. 新建一个GAE项目,定义 WebService 服务类,使用 JDK 6 自带的注释定义方法即可:
@WebService(targetNamespace = "http://www.wegabrow.com/xssfilter")
public class AntiSamyFilter {
@WebMethod
public String Filter(String html) {
try {
CleanResults results = getAntiSamy().scan(html);
return results.getCleanHTML();
} catch (Exception e) {
throw new RuntimeException(e);
}
}
@WebMethod
public String FilterWithConfig(String html, String config) {
try {
ByteArrayInputStream stream = new ByteArrayInputStream(
config.getBytes("UTF-8"));
@SuppressWarnings("deprecation")
Policy policy = Policy.getInstance(stream);
AntiSamy samy = new AntiSamy(policy);
CleanResults results = samy.scan(html);
return results.getCleanHTML();
} catch (Exception e) {
throw new RuntimeException(e);
}
}
private static AntiSamy antiSamy;
static AntiSamy getAntiSamy() throws PolicyException {
if (antiSamy == null) {
createAntiSamy();
}
return antiSamy;
}
static synchronized void createAntiSamy() throws PolicyException {
if (antiSamy == null) {
Policy policy = Policy.getInstance(AntiSamyFilter.class
.getResource("/antisamy-config.xml"));
antiSamy = new AntiSamy(policy);
}
}
}
这里定义了两个WebMethod,Filter 将使用默认配置对Html进行过滤,而 FilterWithConfig 可以使用客户端发送过来的配置进行过滤。
2. 使用 wsgen 工具生成WebService需要用的辅助对象和wsdl文件
按照英文文档里说的,新建一个. sh 文件(文件名无所谓),放在GAE项目的根目录下,右键点击该文件,选择 Open With -> Text Editor,编辑文件内容如下:
class=com.wegabrow.xssfilter.AntiSamyFilter
clpth='./war/WEB-INF/classes'
resourcedir='./war'
outsourcedir='./src'
outdir='./war/WEB-INF/classes'
wsgen -cp "$clpth" -wsdl -keep -r "$resourcedir" -d "$outdir" -s "$outsourcedir" $class
这里说明一下,.sh 文件是 linux shell 脚本文件,在 Windows 下是不能直接运行的。我也懒的去找 Windows 下的 linux shell 工具,于是干脆就把这个文件移植成 Windows bat 文件(需要将扩展名改成.bat),编辑文件内容如下:
set class=com.wegabrow.xssfilter.AntiSamyFilter
set clpth="%CD%\war\WEB-INF\classes"
set resourcedir="%CD%\war"
set outsourcedir="%CD%\src"
set outdir="%CD%\war\WEB-INF\classes"
wsgen -cp %clpth% -wsdl -keep -r %resourcedir% -d %outdir% -s %outsourcedir% %class%
在 eclipse 下是无法运行该 bat 的,需要启动 命令提示符,切换到 GAE 项目目录,然后运行该文件。会生成如下结构的 类 与 wsdl:
3. 建立WebService需要用到的 Adapter 类、 Handler 类和 Servlet 类:
AntiSamyFilterAdapter:
public class AntiSamyFilterAdapter {
private AntiSamyFilter entityAPI = new AntiSamyFilter();
public FilterResponse Filter(Filter request) {
String html = request.getArg0();
String status = entityAPI.Filter(html);
FilterResponse response = new FilterResponse();
response.setReturn(status);
return response;
}
public FilterWithConfigResponse FilterWithConfig(FilterWithConfig request) {
String html = request.getArg0();
String config = request.getArg1();
String status = entityAPI.FilterWithConfig(html, config);
FilterWithConfigResponse response = new FilterWithConfigResponse();
response.setReturn(status);
return response;
}
}
Adaper类非常简单,主要就是接受从Xml防序列化的参数,然后调用具体服务对象的方法,将调用结果生成需要序列化返回客户端的对象。
AntiSamyFilterHandler:
public class AntiSamyFilterHandler {
private static final String NAMESPACE_URI = "http://www.wegabrow.com/xssfilter";
private static final QName FILTER_QNAME = new QName(NAMESPACE_URI, "Filter");
private static final QName FILTER_WITH_CONFIG_QNAME = new QName(
NAMESPACE_URI, "FilterWithConfig");
private MessageFactory messageFactory;
private AntiSamyFilterAdapter xssFilterAdapter;
public AntiSamyFilterHandler() throws SOAPException {
messageFactory = MessageFactory.newInstance();
xssFilterAdapter = new AntiSamyFilterAdapter();
}
@SuppressWarnings("rawtypes")
public SOAPMessage handleSOAPRequest(SOAPMessage request)
throws SOAPException {
SOAPBody soapBody = request.getSOAPBody();
Iterator iterator = soapBody.getChildElements();
Object responsePojo = null;
while (iterator.hasNext()) {
Object next = iterator.next();
if (next instanceof SOAPElement) {
SOAPElement soapElement = (SOAPElement) next;
QName qname = soapElement.getElementQName();
if (FILTER_QNAME.equals(qname)) {
responsePojo = handleFilterRequest(soapElement);
break;
}
else if (FILTER_WITH_CONFIG_QNAME.equals(qname)){
responsePojo = handleFilterWithConfigRequest(soapElement);
break;
}
}
}
SOAPMessage soapResponse = messageFactory.createMessage();
soapBody = soapResponse.getSOAPBody();
if (responsePojo != null) {
JAXB.marshal(responsePojo, new SAAJResult(soapBody));
} else {
SOAPFault fault = soapBody.addFault();
fault.setFaultString("Unrecognized SOAP request.");
}
return soapResponse;
}
private Object handleFilterRequest(SOAPElement soapElement) {
Filter request = JAXB.unmarshal(new DOMSource(
soapElement), Filter.class);
return xssFilterAdapter.Filter(request);
}
private Object handleFilterWithConfigRequest(SOAPElement soapElement) {
FilterWithConfig request = JAXB.unmarshal(new DOMSource(
soapElement), FilterWithConfig.class);
return xssFilterAdapter.FilterWithConfig(request);
}
}
Handler的主要功能就是反序列化Xml数据,然后判断调用哪个Apapter方法,再将结果序列化返回。
AntiSamyFilterSerivceServlet:
public class AntiSamyFilterServiceServlet extends HttpServlet {
/**
*
*/
private static final long serialVersionUID = 1L;
static MessageFactory messageFactory;
static AntiSamyFilterHandler soapHandler;
static String wsdl;
static String xsd;
static {
try {
messageFactory = MessageFactory.newInstance();
soapHandler = new AntiSamyFilterHandler();
wsdl = Utils.getStringFromResource("/AntiSamyFilterService.wsdl")
.replace("/n", "");
xsd = Utils.getStringFromResource(
"/AntiSamyFilterService_schema1.xsd").replace("/n", "");
} catch (Exception ex) {
throw new RuntimeException(ex);
}
}
public void doGet(HttpServletRequest req, HttpServletResponse resp)
throws IOException {
resp.setStatus(HttpServletResponse.SC_OK);
resp.setContentType("text/xml;charset=\"utf-8\"");
if (req.getParameter("wsdl") != null) {
String url = req.getRequestURL().toString();
String respWsdl = wsdl.replace("REPLACE_WITH_ACTUAL_URL", url) // 动态替换服务地址
.replace("REPLACE_WITH_XSD_URL", url + "?xsd"); // 动态替换XSD地址
resp.getWriter().print(respWsdl);
} else if (req.getParameter("xsd") != null) {
resp.getWriter().print(xsd);
} else {
resp.setContentType("text/html;charset=\"utf-8\"");
resp.getWriter()
.print("<html><head><title>xss filter web service</title></head><body>");
resp.getWriter().print("<h1>Xss filter web service.</h1>");
resp.getWriter().print("<div>");
resp.getWriter().print("<a href='?wsdl'>See the wsdl.</a>");
resp.getWriter().print("</div>");
resp.getWriter().print("</body></html>");
}
}
@Override
public void doPost(HttpServletRequest req, HttpServletResponse resp)
throws IOException {
try {
// Get all the headers from the HTTP request
MimeHeaders headers = getHeaders(req);
// Construct a SOAPMessage from the XML in the request body
InputStream is = req.getInputStream();
SOAPMessage soapRequest = messageFactory.createMessage(headers, is);
// Handle soapReqest
SOAPMessage soapResponse = soapHandler
.handleSOAPRequest(soapRequest);
// Write to HttpServeltResponse
resp.setStatus(HttpServletResponse.SC_OK);
resp.setContentType("text/xml;charset=\"utf-8\"");
OutputStream os = resp.getOutputStream();
soapResponse.writeTo(os);
os.flush();
} catch (SOAPException e) {
throw new IOException("Exception while creating SOAP message.", e);
}
}
static MimeHeaders getHeaders(HttpServletRequest req) {
@SuppressWarnings("rawtypes")
Enumeration headerNames = req.getHeaderNames();
MimeHeaders headers = new MimeHeaders();
while (headerNames.hasMoreElements()) {
String headerName = (String) headerNames.nextElement();
String headerValue = req.getHeader(headerName);
StringTokenizer values = new StringTokenizer(headerValue, ",");
while (values.hasMoreTokens()) {
headers.addHeader(headerName, values.nextToken().trim());
}
}
return headers;
}
}
Servlet 处理的就比较简单了,就是具体的输入输出。
这里对原版程序进行了一些改进,一个是支持 Get 方法获取 wsdl 文件。在本项目中,我把 wsdl 和 xsd 文件 copy 到了 src 目录下,并对 wsdl 文件中的 xsd 地址做了处理。可以使用 http://xssfilter.wegabrow.com/XssFilter.asmx?wsdl 的方式获取到wsdl文件,并在访问wsdl地址的时候,动态的替换掉wsdl文件中的服务地址和xsd文件的地址,这样无论服务部署到哪,客户端都可以简单而正确的得到服务的实际地址,避免了调试和发布需要修改wsdl文件中的地址的麻烦。还提供了默认的服务视图。
另外新加的 Utils 类:
public class Utils {
/**
* 读取资源文件
*
* @param user
* @return
*/
public static String getStringFromResource(String resource) {
InputStream inputStream = Utils.class.getResourceAsStream(resource);
String result = readStream(inputStream);
return result;
}
/**
* 从数据流中读取字符串
*
* @param input
* @return
*/
public static String readStream(InputStream input) {
String output = "";
try {
BufferedReader inputReader = new BufferedReader(
new InputStreamReader(input, "UTF-8"));
StringBuffer buffer = new StringBuffer();
String text;
while ((text = inputReader.readLine()) != null) {
buffer.append(text + "/n");
}
output = buffer.toString();
} catch (IOException ioException) {
System.err.println("File Error!");
}
return output;
}
}
到此就完成了制作WebService的全部工作,发布到 GAE 平台上,运行成功。
项目源码下载地址: