支持ajax的靜態頁面生成

目前主流的頁面靜態技術都是基於模板生成的,可是對於一些採用ajax+js渲染的頁面,這種方法是無能爲力的。要解決這個問題,首先要有一個能模擬瀏覽器的運行環境,其餘問題都比較容易解決。能模擬瀏覽器的技術有好多,seleninum , htmlunit等。其中htmlunit是java開發用無界面的瀏覽器,速度和性能很是好,對html建模而且提供API來訪問頁面,點擊連接等等,不須要任務驅動程序 ,提供javascript執行環境,如今不少支持ajax網絡爬蟲也是在它基礎上實現的。 javascript

如何基於htmlunit實現ajax頁面靜態化呢?下面我用一個例子闡述吧,沒什麼比用代碼更直接清楚。這個例子有個ajax渲染的頁面,頁面主要有兩塊內容,頂部是用戶信息,下面是讀取osc 首頁的綜合資訊,基本需求是綜合資訊內容要靜態化,用戶信息不須要。 css

index.jsp頁面代碼 html

<%@ page language="java" contentType="text/html; charset=utf-8"
    pageEncoding="utf-8"%>
<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd">
<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=utf-8">
<title>Insert title here</title>
</head>
<body  whitelist="/userServlet" >
<div id="top"></div>
<h1>osc綜合資訊</h1>
<div id="content"></div>
<div>
<button  onclick="generateStaticHtml(this);">生成靜態頁面</button>
<script type="text/javascript">
//渲染頁面
(function renderPage(){
	
	var xmlhttp = new XMLHttpRequest() ;
	xmlhttp.onreadystatechange=function()
	  {
	  if (xmlhttp.readyState==4 && xmlhttp.status==200)
	    {
	    document.getElementById("top").innerText=xmlhttp.responseText;
	    }
	  }
	xmlhttp.open("GET","${pageContext.request.contextPath }/userServlet",true);
	xmlhttp.send();
	
	var xmlhttp2 = new XMLHttpRequest() ;
	xmlhttp2.onreadystatechange=function()
	  {
	  if (xmlhttp2.readyState==4 && xmlhttp2.status==200)
	    {
	    document.getElementById("content").innerHTML=xmlhttp2.responseText;
	    }
	  }
	xmlhttp2.open("GET","${pageContext.request.contextPath }/contentServlet",true);
	xmlhttp2.send();
	
})() ;

function generateStaticHtml(btn){
	btn.innerText = "在處理中,請稍後"
	var xmlhttp = new XMLHttpRequest() ;
	xmlhttp.onreadystatechange=function()
	  {
	  if (xmlhttp.readyState==4 && xmlhttp.status==200)
	    {
		  btn.innerText ="從新生成" ;
		  window.open("${pageContext.request.contextPath }/index.html") ;
	    }
	  }
	xmlhttp.open("GET","${pageContext.request.contextPath }/generateStaticServlet",true);
	xmlhttp.send();
}

</script>
</div>
</body>
</html>




動態頁面效果 java

注意上面圖的兩個ajax是加載動態內容觸發,而後用javascript渲染到頁面 web

點擊"生成靜態頁面「按鈕會觸發後臺調用靜態組件生成靜態頁面(index.html) ajax

/**
	 * 觸發生成靜態頁面
	 */
	protected void doGet(HttpServletRequest request,
			HttpServletResponse response) throws ServletException, IOException {
		new StaticHtml().process("http://127.0.0.1:8080/ajax/index.jsp", request.getServletContext().getRealPath("/index.html"));
	}



/**
 * 生成靜態頁面組件
 * @author Wen
 *
 */
public class StaticHtml {

	//javascript 攔截   ajax 請求
	private final static String ajaxInterceptJs = "(function(XHR) { "
			+ "var open = XHR.prototype.open;"
			+ "var send = XHR.prototype.send;"
			+ "%s"
			+ "XHR.prototype.open = function(method, url, async, user, pass) {"
			+ "    this._url = url;"
			+ "    open.call(this, method, url, async, user, pass);" 
			+ "};"
			+ "XHR.prototype.send = function(data) {" 
			+ "	if(XHR[this._url]){"
			+ "		this.abort() ;" 
			+ "		delete XHR[this._url] ;" 
			+ "		return  ;"
			+ "	}" + "	send.call(this, data);" + "}" 
			+ "})(XMLHttpRequest);";

	public void process(String dynamicUrl, String staticPath)
			throws FailingHttpStatusCodeException, MalformedURLException,
			IOException {

		final WebClient webClient = new WebClient();
		
		LogAjaxController logAjaxController = new LogAjaxController();

		webClient.setAjaxController(logAjaxController);
		final HtmlPage page = webClient.getPage(dynamicUrl);

		//取出加載頁面過程當中觸發的ajax url
		List<String> ajaxRequests = logAjaxController.getAjaxRequests();
		
		//頁面完整html(包含ajax動態獲取)
		String htmlContent = page.asXml();
		
		
		//頁面還包含ajax加載代碼,須要把這些jax請求攔截下來,可是有些狀況是不需要攔截的就要添加到白名單
		Document document = Jsoup.parse(htmlContent);
		String whitelistStr = document.body().attr("whitelist");
		if (ajaxRequests.size() > 0  && whitelistStr != null) {
			String[] whitelist = whitelistStr.split(",");
			List<String> list = new ArrayList<String>();
			for (String url : ajaxRequests) {
				boolean find = false;
				for (String wlUrl : whitelist) {
					if (url.indexOf(wlUrl) != -1) {
						find = true;
						break;
					}
				}

				if (!find) {
					list.add(url);
				}
			}

			ajaxRequests = list;

		}
		
		if( ajaxRequests.size() > 0 ){
			
			Element script = new Element(Tag.valueOf("script"), "");
			script.attr("type", "text/javascript");
			
			StringBuilder sb  = new StringBuilder() ;
			
			for(String url : ajaxRequests ){
				sb.append("XHR['").append(url).append("']=true;") ;
			}
			
			script.text( String.format(ajaxInterceptJs, sb.toString()) ) ;
			document.head().prependChild(script);//注入攔截ajax js  保證攔截ajax的代碼最早執行
			
		}


		
		//寫入文件
		FileUtils.writeStringToFile(new File(staticPath), document.html() ,"utf-8");

		webClient.closeAllWindows();

	}

	/**
	 * 記錄全部ajax請求url
	 * 
	 * @author Wen
	 *
	 */
	static class LogAjaxController extends NicelyResynchronizingAjaxController {
		private List<String> ajaxRequests = new ArrayList<String>();

		@Override
		public boolean processSynchron(HtmlPage page, WebRequest settings,
				boolean async) {
			ajaxRequests.add(settings.getUrl().getPath());
			return super.processSynchron(page, settings, async);
		}

		public List<String> getAjaxRequests() {
			return Collections.unmodifiableList(ajaxRequests);
		}
	}

}





靜態頁面效果 瀏覽器

頁面效果和動態的index.jsp是同樣的,但此時只有一個ajax請求刷新用戶信息及訪問次數,綜合資訊的內容已經被靜態化的。基本算是實現了個人須要。須要說明有幾個地方。 網絡

如何通htmlunit取得ajax請求的url app

htmlunit提供了處理ajax請求接口,咱們只要簡單繼承NicelyResynchronizingAjaxController這個類,把ajax請求的url記錄下來就能夠了 jsp

靜態頁面也包含ajax加載綜合資訊代碼,這請求是處理攔截下來的

實際上靜態頁面會包含有跟原來頁面如出一轍的ajax加載動態內容代碼,這些代碼對於靜態頁面來講沒有用的,由於內容都被靜態化,不必再發請求加載。咱們通在生成靜態頁面會有頁面注入如下javascript,能夠把不必的請求攔截下來(只攔截一次)

不須要被攔截ajax要怎樣設置

在index.jsp 代碼的body標籤有個whitelist屬性可設置ajax白名單,注入攔截代碼時會讀取這個值過慮掉,默認會攔截掉頁面渲染觸發的全部ajax請求。


4、未解決的問題

htmlunit只能調page.asXml()取頁面html內容,可是這個方法不是很完美,它只是返回標準的xml代碼,會把html的DOCTYPE聲明刪除掉,這個會致使瀏覽解析css會出錯,臨時辦法把<!--?xml version="1.0" encoding="UTF-8"?-->替換回原代碼頁面的DOCTYPE。查遍了htmlunit文檔,都沒有找到能夠直接獲取完整html源代碼的方法,找到的同窗能夠告訴我。

完成的例子代碼下載http://pan.baidu.com/s/15qyPr

相關文章
相關標籤/搜索