前端之家收集整理的这篇文章主要介绍了
支持ajax的静态页面生成,
前端之家小编觉得挺不错的,现在分享给大家,也给大家做个参考。
目前主流的页面静态技术都是基于模板生成的,但是对于一些采用ajax+js渲染的页面,这种方法是无能为力的。要解决这个问题,首先要有一个能模拟浏览器的运行环境,其他问题都比较容易解决。能模拟浏览器的技术有好多,seleninum , htmlunit等。其中htmlunit是java开发用无界面的浏览器,速度和性能非常好,对html建模并且提供API来访问页面,点击链接等等,不需要任务驱动程序,提供javascript执行环境,现在很多支持ajax网络爬虫也是在它基础上实现的。
如何基于htmlunit实现ajax页面静态化呢?下面我用一个例子阐述吧,没什么比用代码更直接清楚。这个例子有个ajax渲染的页面,页面主要有两块内容,顶部是用户信息,下面是读取osc 首页的综合资讯,基本需求是综合资讯内容要静态化,用户信息不需要。
index.jsp页面代码
<%@ 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>
动态页面效果
注意上面图的两个ajax是加载动态内容触发,然后用javascript渲染到页面
点击"生成静态页面“按钮会触发后台调用静态组件生成静态页面(index.html)
@H_
502_28@/**
* 触发
生成静态页面
*/
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"));
}
@H_
502_28@/**
*
生成静态页面组件
* @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,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
htmlunit提供了处理ajax请求接口,我们只要简单继承NicelyResynchronizingAjaxController这个类,把ajax请求的url记录下来就可以了
二、静态页面也包含ajax加载综合资讯代码,这请求是处理拦截下来的
实际上静态页面会包含有跟原来页面一模一样的ajax加载动态内容代码,这些代码对于静态页面来说没有用的,因为内容都被静态化,没必要再发请求加载。我们通在生成静态页面会有页面注入以下javascript,可以把没必要的请求拦截下来(只拦截一次)
三、不需要被拦截ajax要怎样设置
在index.jsp 代码的body标签有个whitelist属性可设置ajax白名单,注入拦截代码时会读取这个值过虑掉,默认会拦截掉页面渲染触发的所有ajax请求。
四、未解决的问题
htmlunit只能调page.asXml()取页面html内容,但是这个方法不是很完美,它只是返回标准的xml代码,会把html的DOCTYPE声明删除掉,这个会导致浏览解析css会出错,临时办法把<!--?xml version="1.0" encoding="UTF-8"?-->替换回原代码页面的DOCTYPE。查遍了htmlunit文档,都没有找到可以直接获取完整html源代码的方法,找到的同学可以告诉我。
完成的例子代码下载http://pan.baidu.com/s/15qyPr
原文链接:https://www.f2er.com/ajax/165491.html