bigpipe探索

1 示例

<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN"
   "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd"><html xmlns="http://www.w3.org/1999/xhtml" xml:lang="de" lang="de"> 
<head> 
<meta http-equiv="Content-type" content="text/html; charset=utf-8" /> 
<meta http-equiv="Content-language" content="de" />
<script type="text/javascript">function arrived(id,text) { var b=document.getElementById(id); b.innerHTML = text; }</script></HEAD><BODY><div>Progressive Loading<div id="content1">-</div>
<div id="content2">-</div>
<div id="content3">-</div>
<div id="content4">-</div>
<div id="content5">-</div>
<div id="content6">-</div>
</div>
<script>arrived("content1", "Wohooo1");</script>
<script>arrived("content2", "Wohooo2");</script>
<script>arrived("content5", "Wohooo5");</script>
</BODY></HTML>

 先输出框架和占位符,然后服务端分步输出脚本,用脚本渲染html

2 flush最终渲染结果是由tomcat容器做的,在 org.apache.catalina.connector.CoyoteAdapter.service(Request, Response)


    /**
     * End request.
     * 
     * @throws IOException an underlying I/O error occurred
     */
    @Override
    public void endRequest()
        throws IOException {
        super.endRequest();
        if (useSocketBuffer) {
            socketBuffer.flushBuffer();
        }
    }
 
 @Override
    public void service(org.apache.coyote.Request req,
                        org.apache.coyote.Response res)
        throws Exception {

        Request request = (Request) req.getNote(ADAPTER_NOTES);
        Response response = (Response) res.getNote(ADAPTER_NOTES);

        if (request == null) {

            // Create objects
            request = connector.createRequest();
            request.setCoyoteRequest(req);
            response = connector.createResponse();
            response.setCoyoteResponse(res);

            // Link objects
            request.setResponse(response);
            response.setRequest(request);

            // Set as notes
            req.setNote(ADAPTER_NOTES, request);
            res.setNote(ADAPTER_NOTES, response);

            // Set query string encoding
            req.getParameters().setQueryStringEncoding
                (connector.getURIEncoding());

        }

        if (connector.getXpoweredBy()) {
            response.addHeader("X-Powered-By", POWERED_BY);
        }

        boolean comet = false;
        boolean async = false;

        try {

            // Parse and set Catalina and configuration specific
            // request parameters
            req.getRequestProcessor().setWorkerThreadName(Thread.currentThread().getName());
            boolean postParseSuccess = postParseRequest(req, request, res, response);
            if (postParseSuccess) {
                //check valves if we support async
                request.setAsyncSupported(connector.getService().getContainer().getPipeline().isAsyncSupported());
                // Calling the container
                connector.getService().getContainer().getPipeline().getFirst().invoke(request, response);

                if (request.isComet()) {
                    if (!response.isClosed() && !response.isError()) {
                        if (request.getAvailable() || (request.getContentLength() > 0 && (!request.isParametersParsed()))) {
                            // Invoke a read event right away if there are available bytes
                            if (event(req, res, SocketStatus.OPEN)) {
                                comet = true;
                                res.action(ActionCode.COMET_BEGIN, null);
                            }
                        } else {
                            comet = true;
                            res.action(ActionCode.COMET_BEGIN, null);
                        }
                    } else {
                        // Clear the filter chain, as otherwise it will not be reset elsewhere
                        // since this is a Comet request
                        request.setFilterChain(null);
                    }
                }

            }
            AsyncContextImpl asyncConImpl = (AsyncContextImpl)request.getAsyncContext();
            if (asyncConImpl != null) {
                async = true;
            } else if (!comet) {
                request.finishRequest();
                response.finishResponse();
                if (postParseSuccess &&
                        request.getMappingData().context != null) {
                    // Log only if processing was invoked.
                    // If postParseRequest() failed, it has already logged it.
                    // If context is null this was the start of a comet request
                    // that failed and has already been logged.
                    ((Context) request.getMappingData().context).logAccess(
                            request, response,
                            System.currentTimeMillis() - req.getStartTime(),
                            false);
                }
                req.action(ActionCode.POST_REQUEST , null);
            }

        } catch (IOException e) {
            // Ignore
        } finally {
            req.getRequestProcessor().setWorkerThreadName(null);
            // Recycle the wrapper request and response
            if (!comet && !async) {
                request.recycle();
                response.recycle();
            } else {
                // Clear converters so that the minimum amount of memory
                // is used by this processor
                request.clearEncoders();
                response.clearEncoders();
            }
        }

    }

而socket的关闭也是在tomcat里的连接器里

org.apache.tomcat.util.net.JIoEndpoint.SocketProcessor.run()

            boolean launch = false;
            synchronized (socket) {
                try {
                    SocketState state = SocketState.OPEN;

                    try {
                        // SSL handshake
                        serverSocketFactory.handshake(socket.getSocket());
                    } catch (Throwable t) {
                        ExceptionUtils.handleThrowable(t);
                        if (log.isDebugEnabled()) {
                            log.debug(sm.getString("endpoint.err.handshake"), t);
                        }
                        // Tell to close the socket
                        state = SocketState.CLOSED;
                    }

                    if ((state != SocketState.CLOSED)) {
                        if (status == null) {
                            state = handler.process(socket, SocketStatus.OPEN);
                        } else {
                            state = handler.process(socket,status);
                        }
                    }
                    if (state == SocketState.CLOSED) {
                        // Close socket
                        if (log.isTraceEnabled()) {
                            log.trace("Closing socket:"+socket);
                        }
                        countDownConnection();
                        try {
                            socket.getSocket().close();
                        } catch (IOException e) {
                            // Ignore
                        }
                    } else if (state == SocketState.OPEN ||
                            state == SocketState.UPGRADING  ||
                            state == SocketState.UPGRADED){
                        socket.setKeptAlive(true);
                        socket.access();
                        launch = true;
                    } else if (state == SocketState.LONG) {
                        socket.access();
                        waitingRequests.add(socket);
                    }
                } finally {
                    if (launch) {
                        try {
                            getExecutor().execute(new SocketProcessor(socket, SocketStatus.OPEN));
                        } catch (NullPointerException npe) {
                            if (running) {
                                log.error(sm.getString("endpoint.launch.fail"),
                                        npe);
                            }
                        }
                    }
                }
            }
            socket = null;
            // Finish up this request
        
 

3 bigpipe关键步骤是分步输出字符流,在一个连接中,一次http请求-响应中分步输出,要保证所有内容都输出后,才能关闭输出流和socket。

如果是单线程的话,不会有任何问题;如果是多线程处理同一个请求的话,要用一些同步工具,比如latch或者barrier。

参考 http://www.searchtb.com/2011/04/an-introduction-to-bigpipe.html

http://codemonkeyism.com/facebook-bigpipe-java/

4 将bigpipe集成到webx里面,首先是页面框架

<script type="text/javascript">function arrivedHtml(id,text) { var b=document.getElementById(id); b.innerHTML = text; }</script></HEAD><BODY>
<div>Progressive Loading
  <div class="pagelet" id="content1">-</div> 
  <div class="pagelet" id="content2">-</div> 
  <div class="pagelet" id="content3">-</div> 
  <div class="pagelet" id="content4">-</div> 
</div> 

然后分别定义每个content页面,如content1

hello world $name

编写bigpipevalve

package com.alibaba.webx.tutorial1.common;

import static com.alibaba.citrus.turbine.TurbineConstant.LAYOUT_TEMPLATE;
import static com.alibaba.citrus.turbine.TurbineConstant.SCREEN_MODULE_NO_TEMPLATE;
import static com.alibaba.citrus.turbine.TurbineConstant.SCREEN_PLACEHOLDER_KEY;
import static com.alibaba.citrus.turbine.TurbineConstant.SCREEN_TEMPLATE;
import static com.alibaba.citrus.turbine.util.TurbineUtil.getTurbineRunData;
import static com.alibaba.citrus.util.Assert.assertNotNull;
import static com.alibaba.citrus.util.BasicConstant.EMPTY_STRING;
import static com.alibaba.citrus.util.ObjectUtil.defaultIfNull;

import java.io.IOException;
import java.util.ArrayList;
import java.util.List;

import javax.servlet.http.HttpServletRequest;

import org.htmlparser.Node;
import org.htmlparser.Parser;
import org.htmlparser.tags.Div;
import org.htmlparser.util.NodeIterator;
import org.htmlparser.util.NodeList;
import org.htmlparser.util.ParserException;
import org.springframework.beans.factory.annotation.Autowired;

import com.alibaba.citrus.service.mappingrule.MappingRuleService;
import com.alibaba.citrus.service.moduleloader.Module;
import com.alibaba.citrus.service.moduleloader.ModuleLoaderException;
import com.alibaba.citrus.service.moduleloader.ModuleLoaderService;
import com.alibaba.citrus.service.moduleloader.ModuleNotFoundException;
import com.alibaba.citrus.service.pipeline.PipelineContext;
import com.alibaba.citrus.service.pipeline.support.AbstractValve;
import com.alibaba.citrus.service.pipeline.support.AbstractValveDefinitionParser;
import com.alibaba.citrus.service.requestcontext.RequestContext;
import com.alibaba.citrus.service.requestcontext.basic.BasicRequestContext;
import com.alibaba.citrus.service.requestcontext.buffered.BufferedRequestContext;
import com.alibaba.citrus.service.template.TemplateException;
import com.alibaba.citrus.service.template.TemplateService;
import com.alibaba.citrus.turbine.Context;
import com.alibaba.citrus.turbine.TurbineConstant;
import com.alibaba.citrus.turbine.TurbineRunData;
import com.alibaba.citrus.turbine.TurbineRunDataInternal;
import com.alibaba.citrus.turbine.support.ContextAdapter;
import com.alibaba.citrus.util.StringUtil;
import com.alibaba.citrus.webx.WebxException;

public class BigPipeValve extends AbstractValve {
	@Autowired
	private BufferedRequestContext bufferedRequestContext;
	@Autowired
	private BasicRequestContext basicRequestContext;
	@Autowired
	private HttpServletRequest request;

	@Autowired
	private TemplateService templateService;

	@Autowired
	private MappingRuleService mappingRuleService;
	@Autowired
	private ModuleLoaderService moduleLoaderService;

	public void invoke(PipelineContext pipelineContext) throws Exception {
		// 拿出request,解析出下一批要执行的screen,flush当前渲染的。
		// 遍历执行PerformScreenValve和RenderTemplateValve
		TurbineRunDataInternal rundata = (TurbineRunDataInternal) getTurbineRunData(request);
		System.out.println("valve started.");
		RequestContext tempRC = basicRequestContext.getWrappedRequestContext();
		String body = bufferedRequestContext.popCharBuffer();
		System.out.println(body);
		List<String> pageletList = parsePagelet(body);
		tempRC.getResponse().getWriter().write(body);
		tempRC.getResponse().getWriter().flush();

		for (String id : pageletList) {
			rundata.setTarget("//" + id);
			rundata.setLayoutEnabled(false);
			performScreenModule(rundata);
			render(rundata);
			body = bufferedRequestContext.popCharBuffer();
			body = renderPagelet(id, body);
			System.out.println(body);
			tempRC.getResponse().getWriter().write(body);
			tempRC.getResponse().getWriter().flush();
		}

		pipelineContext.invokeNext(); // 调用后序valves

		System.out.println("valve ended.");
	}

	private String renderPagelet(String id, String body) {
		body = body.replace("\r\n", "").replace("\n", "").replace("\"", "\'");
		// body = StringEscapeUtils.escapeJavaScript(body);

		String pagelet = "<script>arrivedHtml(\"contentId\", \"body\");</script>";
		return pagelet.replace("contentId", id).replace("body", body);
	}

	private List<String> parsePagelet(String body) throws ParserException {
		Parser parser = new Parser();
		parser.setInputHTML(body);
		NodeIterator iterator = parser.elements();
		Node node = iterator.nextNode();
		node = iterator.nextNode();
		node = iterator.nextNode();
		List<String> pageletList = new ArrayList<String>();
		travel(node, pageletList);
		return pageletList;
	}

	private void travel(Node node, List<String> pageletList) {
		if (node == null) {
			return;
		} else {
			if (node instanceof Div) {
				String cls = ((Div) node).getAttribute("class");
				if (cls != null && cls.contains("pagelet")) {
					System.out.println(((Div) node).getAttribute("id"));
					pageletList.add(((Div) node).getAttribute("id"));
				}

			}

		}

		NodeList childrens = node.getChildren();
		if (childrens == null || childrens.size() == 0) {
			return;
		}
		for (int i = 0; i < childrens.size(); i++) {
			Node temp = childrens.elementAt(i);
			travel(temp, pageletList);
		}
	}

	private void render(TurbineRunDataInternal rundata) throws TemplateException, IOException {
		String target = assertNotNull(rundata.getTarget(), "Target was not specified");
		// 妫�煡閲嶅畾鍚戞爣蹇楋紝濡傛灉鏄噸瀹氬悜锛屽垯涓嶉渶瑕佸皢椤甸潰杈撳嚭銆�
		if (!rundata.isRedirected()) {
			Context context = rundata.getContext();

			renderTemplate(getScreenTemplate(target), context, rundata);

			// layout鍙绂佺敤銆�
			if (rundata.isLayoutEnabled()) {
				String layoutTemplateOverride = rundata.getLayoutTemplateOverride();

				if (layoutTemplateOverride != null) {
					target = layoutTemplateOverride;
				}

				String layoutTemplate = getLayoutTemplate(target);

				if (templateService.exists(layoutTemplate)) {
					String screenContent = defaultIfNull(bufferedRequestContext.popCharBuffer(), EMPTY_STRING);
					context.put(SCREEN_PLACEHOLDER_KEY, screenContent);

					renderTemplate(layoutTemplate, context, rundata);
				}
			}
		}
	}

	/** 璁剧疆content type銆� */
	protected void setContentType(TurbineRunData rundata) {
		// 璁剧疆content
		// type锛屼笉闇�璁剧疆charset锛屽洜涓篠etLocaleRequestContext宸茬粡璁剧疆浜哻harset銆�
		// 閬垮厤瑕嗙洊鍒汉璁剧疆鐨刢ontentType銆�
		if (StringUtil.isEmpty(rundata.getResponse().getContentType())) {
			rundata.getResponse().setContentType("text/html");
		}
	}

	/** 鎵цscreen妯″潡銆� */
	protected void performScreenModule(TurbineRunData rundata) {
		String target = assertNotNull(rundata.getTarget(), "Target was not specified");

		// 浠巘arget涓彇寰梥creen module鍚嶇О
		String moduleName = getModuleName(target);

		try {
			Module module = moduleLoaderService.getModuleQuiet(TurbineConstant.SCREEN_MODULE, moduleName);
			// 褰撴寚瀹氫簡templateName鏃讹紝鍙互娌℃湁鐨剆creen module锛岃�鍗曞崟娓叉煋妯℃澘銆�
			// 杩欐牱灏卞疄鐜颁簡page-driven锛屽嵆鍏堝啓妯℃澘锛屽繀瑕佹椂鍐嶅啓涓�釜module class涓庝箣瀵瑰簲銆�
			if (module != null) {
				module.execute();
			} else {
				if (isScreenModuleRequired()) {
					throw new ModuleNotFoundException("Could not find screen module: " + moduleName);
				}
			}
		} catch (ModuleLoaderException e) {
			throw new WebxException("Failed to load screen module: " + moduleName, e);
		} catch (Exception e) {
			throw new WebxException("Failed to execute screen: " + moduleName, e);
		}
	}

	/** 濡傛灉杩斿洖<code>true</code>锛岄偅涔堝綋妯″潡鎵句笉鍒版椂锛屼細鎶涘紓甯搞�瀛愮被鍙互瑕嗙洊姝ゆ柟娉曪紝浠ユ敼鍙樿涓恒� */
	protected boolean isScreenModuleRequired() {
		return false;
	}

	/** 鏍规嵁target鍙栧緱screen妯″潡鍚嶃�瀛愮被鍙互淇敼鏄犲皠瑙勫垯銆� */
	protected String getModuleName(String target) {
		return mappingRuleService.getMappedName(SCREEN_MODULE_NO_TEMPLATE, target);
	}

	protected String getScreenTemplate(String target) {
		return mappingRuleService.getMappedName(SCREEN_TEMPLATE, target);
	}

	protected String getLayoutTemplate(String target) {
		return mappingRuleService.getMappedName(LAYOUT_TEMPLATE, target);
	}

	protected void renderTemplate(String templateName, Context context, TurbineRunDataInternal rundata)
			throws TemplateException, IOException {
		rundata.pushContext(context);

		try {
			templateService.writeTo(templateName, new ContextAdapter(context), rundata.getResponse().getWriter());
		} finally {
			rundata.popContext();
		}
	}

	public static class DefinitionParser extends AbstractValveDefinitionParser<BigPipeValve> {
	}
}
 

pipeline配置 

<?xml version="1.0" encoding="UTF-8" ?>
<beans:beans xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
             xmlns:services="http://www.alibaba.com/schema/services"
             xmlns:pl-conditions="http://www.alibaba.com/schema/services/pipeline/conditions"
             xmlns:pl-valves="http://www.alibaba.com/schema/services/pipeline/valves"
             xmlns:beans="http://www.springframework.org/schema/beans"
             xmlns:p="http://www.springframework.org/schema/p"
             xsi:schemaLocation="
                http://www.alibaba.com/schema/services http://localhost:8080/schema/services.xsd
                http://www.alibaba.com/schema/services/pipeline/conditions http://localhost:8080/schema/services-pipeline-conditions.xsd
                http://www.alibaba.com/schema/services/pipeline/valves http://localhost:8080/schema/services-pipeline-valves.xsd
                http://www.springframework.org/schema/beans http://localhost:8080/schema/www.springframework.org/schema/beans/spring-beans.xsd
             ">

    <services:pipeline xmlns="http://www.alibaba.com/schema/services/pipeline/valves">

        <!-- 初始化turbine rundata,并在pipelineContext中设置可能会用到的对象(如rundata、utils),以便valve取得。 -->
        <prepareForTurbine />

        <!-- 设置日志系统的上下文,支持把当前请求的详情打印在日志中。 -->
        <setLoggingContext />

        <!-- 分析URL,取得target。 -->
        <analyzeURL />

        <!-- 检查csrf token,防止csrf攻击和重复提交。假如request和session中的token不匹配,则出错,或显示expired页面。 -->
        <checkCsrfToken />

        <loop>
            <choose>
                <when>
                    <!-- 执行带模板的screen,默认有layout。 -->
                    <pl-conditions:target-extension-condition extension="null, vm, jsp, jspx" />
                    <performAction />
                    <performTemplateScreen />
                    <renderTemplate />
                </when>
                <when>
                    <!-- 执行不带模板的screen,默认无layout。 -->
                    <pl-conditions:target-extension-condition extension="do" />
                    <performAction />
                    <performScreen />
                </when>
                <otherwise>
                    <!-- 将控制交还给servlet engine。 -->
                    <exit />
                </otherwise>
            </choose>

            <!-- 假如rundata.setRedirectTarget()被设置,则循环,否则退出循环。 -->
            <breakUnlessTargetRedirected />
        </loop>
	<valve class="com.alibaba.webx.tutorial1.common.BigPipeValve" />
    </services:pipeline>

</beans:beans>
 

猜你喜欢

转载自hill007299.iteye.com/blog/1576096
今日推荐