在 Google AppEngine 下部署 WebService(Java)

在 Google AppEngine 上部署 WebService(Java)

最近玩了玩 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:

在 Google AppEngine 下部署 WebService(Java)

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 平台上,运行成功。


项目源码下载地址:

http://download.csdn.net/detail/lxfan/4758624