OpenFire源码学习之十二:HttpBind&Script Syntax

HttpSessionManager

该类管理所有通过httpbing连接到openfire的议定。它是一个同步http的双向流

http://www.xmpp.org/extensions/xep-0124.html

构造方法:HttpSessionManager()

配置一个汇集执行者对异步路由传进来的数据的默认大小配置默认为60秒

注意:在默认情况下,服务支持最大254个客户端。这时候BOSH 承载着非常大的负荷,那么这就需要额外的分配一些线程池容量以供客户端及时入站点。

public HttpSessionManager() {
        this.sessionManager = SessionManager.getInstance();
        
        int poolSize = JiveGlobals.getIntProperty("xmpp.httpbind.worker.threads", 
				JiveGlobals.getIntProperty("xmpp.client.processing.threads", 16));
        int keepAlive = JiveGlobals.getIntProperty("xmpp.httpbind.worker.timeout", 60);

        sendPacketPool = new ThreadPoolExecutor(poolSize, poolSize, keepAlive, TimeUnit.SECONDS, 
			new LinkedBlockingQueue<Runnable>(), // unbounded task queue
	        new ThreadFactory() { // custom thread factory for BOSH workers
	            final AtomicInteger counter = new AtomicInteger(1);
	            public Thread newThread(Runnable runnable) {
	                Thread thread = new Thread(Thread.currentThread().getThreadGroup(), runnable,
	                                    "httpbind-worker-" + counter.getAndIncrement());
	                thread.setDaemon(true);
	                return thread;
	            }
	    	});
    }

ThreadPoolExecutor该线程池配置了:池中所保持的线程数和最大线程数均为poolSize(16),

keepAlive多余空闲线程等待心任务的的最长时间。

TimeUnit.SECONDS:参数的时间单位

newLinkedBlockingQueue<Runnable>():执行前保持的队列,此队列仅保持由execute 方法提交的 Runnable 任务。

ThreadFactory:执行程序创建新线程时使用的工厂。

 

启动sessionManager

public void start() {
        inactivityTask = new HttpSessionReaper();
        TaskEngine.getInstance().schedule(inactivityTask, 30 * JiveConstants.SECOND,
                30 * JiveConstants.SECOND);
        sendPacketPool.prestartCoreThread();
    }

HttpSessionReaper该类是个记时任务类。覆盖了TimerTask中的run方法。我们先看看该方法的内容

public void run() {
            long currentTime = System.currentTimeMillis();
            for (HttpSession session : sessionMap.values()) {
                long lastActive = currentTime - session.getLastActivity();
                if (Log.isDebugEnabled()) {
                	Log.debug("Session was last active " + lastActive + " ms ago: " + session.getAddress());
                }
                if (lastActive > session.getInactivityTimeout() * JiveConstants.SECOND) {
                	Log.info("Closing idle session: " + session.getAddress());
                    session.close();
                }

            }
        }

session.getLastActivity():这个方法以毫秒为时间单位返回关闭http连接的时间。

getInactivityTimeout() 这个方法以秒为单位返回不活跃或被终止会话时间

这里用系统时间减去http关闭连接时间。也就是说在关闭连接的时间与系统当先的这段时间,该会话在服务端系统就是一个闲置的状态。下面的方法:

if (lastActive > session.getInactivityTimeout() * JiveConstants.SECOND) {
                	Log.info("Closing idle session: " + session.getAddress());
                    session.close();
                }

当这个闲置时间大于这个不活跃或被关闭连接的时候,这里就执行session.close()方法来结束这个会话。

 

回过头来,再看start()方法。

 TaskEngine.getInstance().schedule(inactivityTask, 30 * JiveConstants.SECOND,30 * JiveConstants.SECOND);

TaskEngine该类使用工作线程来执行任务。它还可以安排任务执行的时间,该类模拟了ExecutorService和Timer。任何TimerTask,按预定计划运行在未来将会被自动运行使用线程执行器的线程池。这意味着标准的限制,TimerTasks并不适用快速运行。

Schedule()方法:

安排指定重复任务(固定的延迟执行)。Subsequent executions take place at approximatelyregular intervals separated by the specified period.

在固定延迟执行,每个执行计划相对于实际执行时间前执行。如果一个执行延迟由于任何原因(如垃圾回收或其他后台活动),后续执行也将推迟。从长远来看,执行的频率通常是略低于指定的周期的倒数(假设系统时钟底层对象等(长)是准确的)。

固定延迟执行适当的重复出现的活动,要求“smoothness“换句话说,它是适合他们的活动,更重要的是要保持频率准确在短期内比从长远来看。这包括大多数动画任务,如闪烁的光标定期。它还包括任务中执行常规的活动在回应人类输入,如自动重复字符只要一个关键是压低频率。

 

调用该方法的三个参数:

inactivityTask:需要调度的任务

30 * JiveConstants.SECOND执行任务之前延迟的毫秒数

30 * JiveConstants.SECOND连续执行任务的间隔毫秒

 

最后一步:

sendPacketPool.prestartCoreThread();

该方法启动核心线程,使其处于等待工作的空闲状态。仅当执行新任务时,此操作才重写默认的启动核心线程策略。

HttpBindServer

public void setHttpBindPorts(int unsecurePort, int securePort) throws Exception {
        changeHttpBindPorts(unsecurePort, securePort);
        bindPort = unsecurePort;
        bindSecurePort = securePort;
        if (unsecurePort != HTTP_BIND_PORT_DEFAULT) {
            JiveGlobals.setProperty(HTTP_BIND_PORT, String.valueOf(unsecurePort));
        }
        else {
            JiveGlobals.deleteProperty(HTTP_BIND_PORT);
        }
        if (securePort != HTTP_BIND_SECURE_PORT_DEFAULT) {
            JiveGlobals.setProperty(HTTP_BIND_SECURE_PORT, String.valueOf(securePort));
        }
        else {
            JiveGlobals.deleteProperty(HTTP_BIND_SECURE_PORT);
        }
    }

首先看看changeHttpBindPorts(unsecurePort, securePort)方法:

private synchronized void changeHttpBindPorts(int unsecurePort, int securePort)
            throws Exception {
        .......

        if (httpBindServer != null) {
            try {
                httpBindServer.stop();
            }
            catch (Exception e) {
                Log.error("Error stopping http bind server", e);
            }
        }

        configureHttpBindServer(unsecurePort, securePort);
        httpBindServer.start();
    }

在这段代码里面主要设计到的有两个方法

方法一:confingureHttpBindServer(unsecurePort,securePort)

该方法主要为一个httpbind指定两个端口:

一个是:正常httpbind服务端口,7070另一个为:TLS安全HTTP绑定端口。7443

该方法中用到了一个jetty类QueuedThreadPool。

解释下一个类。该类是jetty的一个线程池,它实现了org.eclipse.jetty.util.thread.ThreadPool接口,并继承org.eclipse.jetty.util.component.AbstractLifeCycle。

在里是为了给该线程池设置一线程名称。

 

然后创建连接,of与jetty中创建连接用到了SelectChannelConnector。它继承了

AbstractNIOConnector。它选择NIO连接器。这个连接器使用NIO缓冲器与非有效阻止线程模型。直接使用NIO缓冲区和线程只分配给连接请求。同步是用来模拟阻塞为servletAPI,和任何冲内容尽头的请求处理是异步写的。这个连接器是最好的用于当有许多空闲时间的连接。

 

使用延续,支持无线的等待。如果一个过滤器或servlet返回后调用Continuation.suspend()或者延续暂停期间抛出一个运行时异常时从一个调用Continuation.undispatch(),Jetty将不会发送响应给客户端。相反,线程被释放和延续是放置在定时器队列。如果连续超时过期,或它的恢复方法被调用,然后再次请求分配给一个线程和请求重试。这种方法的局限性是,请求内容不可用于重试请求,因此如果可能应该阅读后的延续或保存为请求属性或相关的对象实例的延续。

 

createBoshHandler()

private void createBoshHandler(ContextHandlerCollection contexts, String boshPath)
    {
        ServletContextHandler context = new ServletContextHandler(contexts, boshPath, ServletContextHandler.SESSIONS);
        context.addServlet(new ServletHolder(new HttpBindServlet()),"/*");
    }
createCrossDomainHandler()
 private void createCrossDomainHandler(ContextHandlerCollection contexts, String crossPath)
    {
        ServletContextHandler context = new ServletContextHandler(contexts, crossPath, ServletContextHandler.SESSIONS);
        context.addServlet(new ServletHolder(new FlashCrossDomainServlet()),"/crossdomain.xml");
    }

loadStaticDirectory()

最后一步:

httpBindServer.start()

Script Syntax(脚本语法)

介绍

一些运行时环境的跨域安全限制,允许客户端访问纯XML文本,只有当它接收到一个特定的服务器(例如,Web客户端的主机名下载)。出人意料的是,同样的环境通常允许客户端接收和执行来自任何服务器的脚本。安全注意事项以下部分描述了重大部署脚本语法风险

Script Syntax

1、Requesting Use of Script Syntax

BOSH的客户端可以发送一个BOSH连接管理器使用脚本语法,而不是纯粹的语法会话请求。如果连接管理器支持脚本语法,那么它必须发送它的会话创建响应使用脚本语法,并在会话中的所有后续客户端请求和连接管理器响应必须发送使用脚本语法。如果连接管理器没有支持“BOTH脚本”语法,那么它会给终端返回一个 ‘item-not-found‘ 或者HTTP404(未找到)来响应客户端的会话请求。

注意:HTTP响应的BODY在下面的例子中只包含换行符,以提高可读性。在实践中,必须是没有换行符。

例一:Script Syntax不支持绑定错误

HTTP/1.1 200 OK
Content-Type: text/javascript; charset=utf-8
Cache-Control: no-store
Cache-Control: no-cache
Pragma: no-cache
Content-Length: 212

_BOSH_("<body type='terminate' condition='item-not-found'
        xmlns='http://jabber.org/protocol/httpbind'/>")

例二:Script Syntax不支持http错误

HTTP/1.1 404 Not Found
Content
-Length: 0

2.变更请求语法

客户端必须进行以下更改他们的请求转换脚本语法:

1.Certain字节的UTF-8编码的<body/>元素,根据字节内定义的URIRFC3986[3]为逃避规则。因此,所有的八位位组,除了那些代表7位字母数字字符或字符 - _?!$()*+=@/?应被取代与字符三重峰,组成的百分比字符由两个16进制码表示的八位位组的值的

2.A‘字符和URI编码<body/>元素,必须附加到URI连接管理器内运行其服务器。

3 URI必须在HTTP GET请求发送到连接管理器。

4.Include额外的HTTP标头,以防止请求/响应缓存或存储任何中介。

注:GET1.1HTTP GET标题行下面的两个例子中之间的所有空白只包括以提高可读性。在实践中不能有空格。

3:请求BOSH会话脚本语法

GET /webclient?%3Cbody%20content='text/xml;%20charset=utf-8'%20
    hold='1'%20rid='1573741820'%20to='jabber.org'%20
    route='xmpp:jabber.org:9999'%20secure='true'%20ver='1.6'%20
    wait='60'%20xml:lang='en'%20
    xmlns='http://jabber.org/protocol/httpbind'/%3E
    HTTP/1.1
Host: httpcm.jabber.org
Accept-Encoding: gzip, deflate
Cache-Control: no-store
Cache-Control: no-cache
Pragma: no-cache
Content-Length: 0

4:脚本语法传输节点

GET /webclient?%3Cbody%20rid='1249243562'%20sid='SomeSID'%20
    xmlns='http://jabber.org/protocol/httpbind'%3E%3C
    message%20to='[email protected]'%20xmlns='jabber:client'%3E%3C
    body%3EI%20said%20%22Hi!%22%3C/body%3E%3C/message%3E%3C/body%3E
    HTTP/1.1
Host: httpcm.jabber.org
Accept-Encoding: gzip, deflate
Cache-Control: no-store
Cache-Control: no-cache
Pragma: no-cache
Content-Length: 0

虽然2616不限制长度的HTTP URIs中,可能会限制客户端的运行时环境,它可以包括在每个GET请求的URI长度。 [4]在这种情况下,客户端必须减少的<body/>元素的内容,并在随后的HTTP GET请求包裹在新的<body/>元素(与递增远离属性)发送的其它的内容。这是可能的,因为不像纯语法,脚本语法的连接管理器必须把一串字符之间的打开和关闭每个请求的<BODY>标签的字节流的一部分,而不是作为一套完整的XML节。任何一个<body/>元素的内容必须不能被解析其余的流隔离。

Changes to the Response Syntax

连接管理器必须进行以下更改他们的反应,转换脚本语法:

1.Certain字符必须更换内的<body/>元素的根据规则的ECMAScript定义的字符串内转义字符。必要的替换概述于下表。

1:字符替换

字符
	Unicode代码点值
	转义序列

"	U+0022	\"
Line Feed (New Line)	U+000A	\n
Carriage Return	U+000D	\r
Line Separator	U+2028	\u2028
Paragraph Separator	U+2029	\u2029
\	U+005C	\\

每个Unicode格式控制字符(即字符类别CFUNICODE字符数据库,例如,左到右的商标或从右到左标记)也必须被取代它的Unicode转义序列(例如\u200e u200f

2。下面的八个字符必须追加到的<body/>元素:

_BOSH_("

3,下面的两个字符必须附加到的<body/>元素:

")

4如果客户端请求不拥有内容属性,那么HTTP响应的Content-Type头必须是/ javascript中的字符集= UTF-8应用程序/x-javascript的字符集= UTF-8

5.Include额外的HTTP标头,以防止缓存或存储任何中介。

注:在下面的两个例子的HTTP响应的尸体仅包括所有的换行都以提高可读性。在实践中,必须是没有换行符。

Example 5. Session creation response in Script Syntax

HTTP/1.1200 OK
Content-Type: text/javascript; charset=utf-8
Cache-Control: no-store
Cache-Control: no-cache
Pragma: no-cache
Content-Length: 233

_BOSH_("<body wait=‘60‘ inactivity=‘30‘ polling=‘5‘ requests=‘2‘hold=‘1‘
        ack=‘1573741820‘accept=‘deflate,gzip‘ maxpause=‘120‘ sid=‘SomeSID‘
        charsets=‘ISO_8859-1 ISO-2022-JP‘ver=‘1.6‘ from=‘jabber.org‘
        secure=‘true‘xmlns=‘http://jabber.org/protocol/httpbind‘/>")

















郑重声明:本站内容如果来自互联网及其他传播媒体,其版权均属原媒体及文章作者所有。转载目的在于传递更多信息及用于网络分享,并不代表本站赞同其观点和对其真实性负责,也不构成任何其他建议。