最近玩了玩 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 平臺上,運行成功。
項目源碼下載地址: