http中chunked编码的特殊编码头

在进行Http传输中,可以通过设置Transfer-Encoding以进行传输编码。其中chunked编码表示分块进行数据传输,其通过一个0长度的chunk块表示数据传输完毕。对于在传输过程中的chunked块,则通过以下的一个数据格式进行描述:

chunk-size 
[ chunk-extension ] 
CRLF 
chunk-data 
CRLF

不考虑chunked-extension属性,则可以理解为先描述块长度,然后接回车换行,接下来再描述相应长度的数据信息,最后接回车换行符,即完成一个chunk编码信息。

这里的chunk-size,与其它的传输不同,这里需要设置为 16 进制数字字符串。即不是传输认为的设置一个长度值即可。对于一个长度值为255的chunk块,其16进制为ff(小写表示),直接编码即为 ff。但这里要求为数字字符串,即以字符串的方式来描述编码值,编码为'f''f',针对f字符的编码值为 66(16进制),那么255长度的实际编码值即为 66 66。

一个255长度的编码片断,类似如下所示:

如上所示的标记处,前面为头结束的2个CRLF值。

在Http编码中,只有这里的chunked编码有点特别。其它的长度信息如content-length值等,都是使用正常的长度值(16进制),确实有点奇怪。在编码时,如果对这里的编码头没写正确,那么输出到客户端的数据永远都是错的…

transfer-encoding和content-length的不同实现

前段时间在项目中看到如下的代码:

		HttpServletResponse response = (HttpServletResponse)servletResponse;
		response.setHeader("Transfer-Encoding", "utf8");
		filterChain.doFilter(servletRequest, servletResponse);

原意是想对输出的内容进行编码,却用错了响应头,结果这个错误的响应头对后面的客户端程序带来了许多麻烦。这里有必要对这个这块的内容进行详细地了解。

传输数据编码:Transfer-Encoding
数据编码,即表示数据在网络传输当中,使用怎么样的保证方式来保证数据是安全成功地传输处理。可以是分段传输,也可以是不分段,直接使用原数据进行传输。
有效的值为:Trunked和Identity.
传输内容编码:Content-Encoding
内容编码,即整个数据信息是在数据器端经过怎样的编码处理,然后客户端会以怎么的编码来反向处理,以得到原始的内容。这里的内容编码主要是指压缩编码,即服务器端压缩,客户端解压缩。
可以参考的值为:gzip,compress,deflate和identity。
传输内容格式:Content-Type
内容格式,即接收的数据最终是以何种的形式显示在浏览器中。可以是一个图片,还是一段文本,或者是一段html。内容格式额外支持可选参数,charset,即实际内容的字符集。通过字符集,客户端可以对数据进行解编码,以最终显示可以看得懂的文字(而不是一段byte[]或者是乱码)。

3种描述信息,可以由下图来表示(来源于《Http权威指南》):

从上文中,可以看出,实际上原filter中的内容可能是想表达以下的意思:

response.setContentType("text/html;charset=UTF8");
//或者
response.setContentType("application/json;charset=UTF8");

继续阅读“transfer-encoding和content-length的不同实现”

nio通讯中cpu 100%的问题及处理(转)

本文转自:http://marlonyao.iteye.com/blog/1005690 原文作者:marlonyao

在经典的nio通讯例子中,通常是这样写的:

if (key.isAcceptable()) {  
                        ServerSocketChannel server = (ServerSocketChannel) key.channel();  
                        SocketChannel client = server.accept();  
                        System.out.println("Accepted connection from " + client);  
                        client.configureBlocking(false);  
//开始注册读写事件
                        SelectionKey clientKey = client.register(selector, SelectionKey.OP_WRITE|SelectionKey.OP_READ);  
}
//开始写
if (key.isWritable()) {  
                        // System.out.println("is writable...");  
                        SocketChannel client = (SocketChannel) key.channel();  
                        ByteBuffer buffer = (ByteBuffer) key.attachment();  
                        buffer.flip();  
                        client.write(buffer);  
                        buffer.compact();  
                    }  

在测试例子中,该例子也能够正常的输出,不过就是有一个现象:

但是如果你这时top用看一下发现服务器进程CPU占用到95%以上,如果取消掉32行的注释,服务器会不断地输出"is writable…",这是为什么呢?让我们来分析当第一个客户端连接上时发生什么情况。

  1. 在连接之前,服务器第11行:selector.select()处阻塞。当阻塞时,内核会将这个进程调度至休眠状态,此时基本不耗CPU。
  2.     当客户端发起一个连接时,服务器检测到客户端连接,selector.select()返回。selector.selectedKeys()返回已就绪的SelectionKey的集合,在这种情况下,它只包含一个key,也就是53行注册的acceptable key。服务器开始运行17-25行的代码,server.accept()返回代码客户端连接的socket,第22行在socket上注册OP_READ和OP_WRITE,表示当socket可读或者可写时就会通知selector。
  3.     接着服务器又回到第11行,尽管这时客户端还没有任何输入,但这时selector.select()不会阻塞,因为22行在socket注册了写操作,而socket只要send buffer不满就可以写,刚开始send buffer为空,socket总是可以写,于是server.select()立即返回,包含在22行注册的key。由于这个key可写,所以服务器会运行31-38行的代码,但是这时buffer为空,client.write(buffer)没有向socket写任何东西,立即返回0。
  4.     接着服务器又回到第11行,由于客户端连接socket可以写,这时selector.select()会立即返回,然后运行31-38行的代码,像步骤3一样,由于buffer为空,服务器没有干任何事又返回到第11行,这样不断循环,服务器却实际没有干事情,却耗大量的CPU。

从上面的分析可以看出问题在于我们在没有数据可写时就在socket上注册了OP_WRITE,导致服务器浪费大量CPU资源,解决办法是只有数据可以写时才注册OP_WRITE操作。上面的版本还不只浪费CPU那么简单,它还可能导致潜在的死锁。虽然死锁在我的机器上没有发生,对于这个简单的例子似乎也不大可能发生在别的机器上,但是在对于复杂的情况,比如我写的端口转发工具中就发生了,这还依赖于jdk的实现。

继续阅读“nio通讯中cpu 100%的问题及处理(转)”

tomcat如何解析resource数据源

我们都知道在tomcat中,可以通过在context.xml中配置resource中用于配置tomcat数据源,如下所示即是一个配置例子。

<Context>
	<Resource name="jdbc/xx" auth="Container" type="javax.sql.DataSource" password="mymysql"
			  driverClassName="com.mysql.jdbc.Driver"
			  username="root" url="jdbc:mysql://127.0.0.1/xx"
			  />
</Context>

配置了如上的数据源之后,在java代码中,就可以以如下代码进行访问:

         InitialContext initialContext = new InitialContext();
	DataSource dataSource = (DataSource) initialContext.lookup("java:comp/env/jdbc/xx");

那么,tomcat是如何将resource中的信息解析成上下文中,并可以通过jndi的方式进行访问呢。这就得从contextResource对象的创建说起。

在NamingContextListener中,namingContext被创建,同时相应的comp上下文和evn上下文被创建起来。然后通过解析context.xml,将最终的jdbc/xx节点绑定在相应的上下文中,并通过解析Resource节点,最终确定数据源对象的创建。

继续阅读“tomcat如何解析resource数据源”

在embed tomcat中使用jndi命名服务

在嵌入式的tomcat中,默认是不开启命名服务支持的。如果要使用命名服务,按照官方的意思,只需要增加以下代码即可:

Tomcat tomcat = new Tomcat();
tomcat.enableNaming();

这样即可,但实际上,这个tomcat.enableNaming里面的东西太多。实际上,只需要增加以下代码即可: 

        System.setProperty("catalina.useNaming", "true");//开启命名服务支持
        String value = "org.apache.naming";
        System.setProperty(javax.naming.Context.URL_PKG_PREFIXES, value);//注册命名服务URL前缀

在如上的代码中,并不需要增加关于INITIAL_CONTEXT_FACTORY的设置,因为在访问资源时,如访问java:comp/env/jdbc/xx.tomcat只需要从URL_PKG_PREFIXES所在包下,寻找以URL_PKG_PREFIXES.java.javaURLContextFactory的类即可,而tomcat中有此类。并且查找的context是URLContext,而并不是InitialContext,因此其他的代码都是不需要的。

在具体的解析中,类StandardContext中进行启动时,只需要判断System.getProperty("catalina.useNaming")即可判断出是否启动命名上下文。如果启用命名服务,则会自动将NamingContextListener注册到监听器中,余下的工作就是监听器去完成了。详细的代码如下所示:(以下代码在类StandardContext中的startInternal方法内)

        String useNamingProperty = System.getProperty("catalina.useNaming");
 ......

        if (ok && isUseNaming()) {
            if (getNamingContextListener() == null) {
                NamingContextListener ncl = new NamingContextListener();
                ncl.setName(getNamingContextName());
                addLifecycleListener(ncl);
                setNamingContextListener(ncl);
            }
        }
//启动lifecycle,即刚注入的监听器,以及其他信息
fireLifecycleEvent(Lifecycle.CONFIGURE_START_EVENT, null);

使用scribe开发微博类oauth调用

DefaultApi20前篇文章说了如何使用基本的http api来调用新浪微博的应用,而在登陆这方面,新浪使用的是基本oauth2.0的授权应用,而其它如网易,腾讯则是使用基本1.0的调用,那么这两种应用都是基本oauth授权的应用,肯定有相应统一的开发技术了,那就是scribe。

关于scribe,可以访问其官方网站:https://github.com/fernandezpablo85/scribe-java,当然使用maven的话,可以使用以下的引用将其引用到项目中来:

		<dependency>
			<groupId>org.scribe</groupId>
			<artifactId>scribe</artifactId>
			<version>1.3.0</version>
		</dependency>

scribe支持oauth1.0a以及oauth2.0两个版本的授权应用。关于1.0和2.0的区别可以google进行查看,总的来说2.0是要比1.0要好些,应用也简单些。但每个提供商所支持的版本也不尽相同,所以本文分别演示两个版本的应用。

由于腾讯和网易所支持的版本均为1.0a,而新浪为2.0,所以本文仅给出参考代码,具体的代码,都差不多,无非就是根据不同的提供商的请求地址和参数进行修改的变化而已,主要的代码还是不变的。
备注:由于腾讯和网易在不给call_back参数的时候,需要设置为null,而不是标准中的oob,此处特地注明。
在scribe应用中,已经提供了国内主要微博和社区的oauth service api,其中有人人,新浪(仅为1.0a),腾讯和网易,所以本文也不需要额外再写了(当然要写的话还是很简单的)。这里要感谢Artorius,他的文章为:http://artori.us/java-oauth-lib-scribe-java-start-supporting-weibo

继续阅读“使用scribe开发微博类oauth调用”