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的不同实现”

会话Cookie及session的关系(Cookie & Session)

在通常的使用中,我们只知道session信息是存放在服务器端,而cookie是存放在客户端。但服务器如何使用session和客户端之间进行通信,以及jsessionId是怎么回事,这并没有一个完整和正确的认识,因此这里将这类信息汇总。

session中的jsessionId是在session创建好之后,发送给客户端。然后在每一次请求中,客户端即会将这个信息传递给服务器端,服务器端使用这个信息来维护和客户端之间的会话通信,在浏览器关闭之后,这个session就消失了。
而对于普通的cookie来说,它是有着一定的时间存放在客户端的机器中的。当下次打开浏览器时,这个cookie就会被读取,同时在请求时发放到服务器端。在关闭浏览器,这个cookie仍然存在的,并没有消失。
那么,这两者之间有没有联系呢,这就要看官方对于Cookie的不同分类,其实就对应着session Cookie和psersistent Cookie的描述了。如下所示:

Session Cookie
A user's session cookie[15] (also known as an in-memory cookie or transient cookie) for a website exists in temporary memory only while the user is reading and navigating the website. When an expiry date or validity interval is not set at cookie creation time, a session cookie is created. Web browsers normally delete session cookies when the user closes the browser.

Persistent cookie
A persistent cookie[15] will outlast user sessions. If a persistent cookie has its Max-Age set to 1 year (for example), then, during that year, the initial value set in that cookie would be sent back to the server every time the user visited the server. This could be used to record a vital piece of information such as how the user initially came to this website. For this reason, persistent cookies are also called tracking cookies.

从上面的描述中看,所谓的session Cookie,就是我们所说的session,其实就是一个没有设置过期时间的cookie信息。而普通的cookie,即我们通常所说的cookie,就是设置了过期时间(有效时间,通常大于一个特定的值,如一周)的cookie。

那么,我们可以这样认为。session就是服务器端,通过使用session Cookie来维系客户端和服务器的关系,而所谓的存储就是在服务器端针对这个session值(如jsessionId值)作了一个内部的增强,可以围绕着这个session Cookie创建一个单独的数据存储器(如map),然后来实现我们所谓的session数据存储呢。既然这样,在一些特定的场景(如分布式环境),是否可以按照这种设计思路设计另外的session存储呢,这也是可以的。

继续阅读“会话Cookie及session的关系(Cookie & Session)”

Oracle中关于rownum和分页的详细描述(翻译)

原文为:http://www.oracle.com/technetwork/issue-archive/2006/06-sep/o56asktom-086197.html
Limiting Result Sets
rownum是一个很让人疑惑的oracle概念,这会导致很多人不会使用它,一般情况下,在以下的两个场合,我们会使用到rownum伪列:

  • 执行一个TOP-N查询,如查询最大或最多,最靠前的某些记录,与其它数据库(如mysql)的limit很相似.
  • 对查询进行分页,这个很常见,就不说了。

How ROWNUM Works
rownum是一个伪列,在一个查询中,它并不是真正的列,但在查询结果中就会存在这个列数据。在查询的结果中,rownum会依次从1开始赋值给查询的每一条记录。但这个值并不是固定的,会随着不同的查询发生变化,也就是说在一个数据表中,并不存在rownum这个列,即你不能在一个表中查询rownum为5的数据,没有这种数据。

那么rownum是什么时候被赋值的呢,了解这个时机会让你在一些查询中了解一个sql为什么要这样写。
rownum是在当数据查询一定的条件被查询出来但并没有被排序或者聚合的时候赋值的
也就是说,rownum是在有数据的时候才会递增,如果没有数据被查询出来,则不会递增。如下面的sql语句,则永远不会返回任何数据:

select * 
  from t 
 where ROWNUM > 1;

因为对于第一条数据来说,rowum > 1并不为真,因此rownum也不会递增到2,所以不会有数据的rownum大于1。
我们可以通过下面的一个标准的数据查询,来了解rownum是何时被赋值的。如下所示:

select ..., ROWNUM
  from t
 where <where clause>
 group by <columns>
having <having clause>
 order by <columns>;

那么,一般来说,它会按照以下的顺序来工作:

  1. The FROM/WHERE clause goes first.
  2. ROWNUM is assigned and incremented to each output row from the FROM/WHERE clause.
  3. SELECT is applied.
  4. GROUP BY is applied.
  5. HAVING is applied.
  6. ORDER BY is applied.

继续阅读“Oracle中关于rownum和分页的详细描述(翻译)”

在windows x64环境下使用oracle instant client配置pl/sql developer

在x64的环境下,即便安装了oracle数据库,但是如果使用pl/sql developer,那么是使用不了的。原因很简单,pl/sql 是32位的,而所安装的oracle为64位的。当然,如果再安装一个32位的客户端工具,那么就可以使用了。但是,仅仅为使用pl/sql而安装一个600M左右的oracle客户端,这个代价是不是太大了。

而实际上,pl/sql工具只需要依赖一个叫oci.dll的动态链接库就可以了。然后就是基于tnsname.ora的配置文件,以让pl/sql知道需要以何种方式访问数据库信息,即可以了。那么,哪些东西可以满足这两个条件呢,那就是oracle instant client。

网上的大多数的配置都需要建立developer.bat文件,或者修改环境变量,或者修改注册表。这些修改都对现有的使用方式以及会有潜在的问题,比如本机已有oracle数据库的情况。本文仅从pl/sql本身所支持的信息出发,不修改文件,环境变量以及注册表信息,采用最常用的惯例配置达到让pl/sql自动发现配置信息的目的。

1    首先下载oracle instant client文件,下载地址为 http://download.oracle.com/otn/nt/instantclient/112010/instantclient-basic-win32-11.2.0.1.0.zip。将其解压在某一个目录,暂称之为 F:\plsql\instantclient_11_2目录。

2    打开pl/sql,在tools->Preference->Connection里,修改配置里面的Oracle Home项,将其修改为我们刚才解压的目录。同时修改下面的OCI Library项,将其修改为解压目录\oci.dll。即可。保存配置

3    从别处或者已安装oracle的安装目录copy一个tnsname.ora文件,将其放在 解压目录\NETWORK\ADMIN目录下即可。

经过以上3个步骤,再重新打开pl/sql,我们就会发现pl/sql已经能够自动地从我们刚copy的tnsname.ora文件中读取数据库配置信息了,输入帐号名/密码,就和平时所使用的情况一样了。
这里需要注意的是,pl/sql读取的不是已安装的数据库的tnsnames.ora,而是我们copy过去的新的tnsnames.ora文件。一个系统中有2个tnsnames.ora是不是很难维护,那么还有办法。如下所示.

如果你的电脑是windows 7,windows 8或者windows 2008,那么恭喜你,你可以使用linux中的link,即软连接。何为软连接,这里不再叙述。我们只需要在新的解压目录下的NETWORK\ADMIN目录下,创建一个名为tnsnames.ora的软连接,并连接到64位的oracle下的tnsnames.ora文件,就可以了。这样,修改其中1个,都修改了(实际就是一个文件),就不需要再费心维护2个文件了。以下代码即可(请在cmd下运行):

F:\plsql\instantclient_11_2\NETWORK\ADMIN>mklink tnsnames.ora F:\oracle\product\11.2.0\dbhome_1\NETWORK\ADMIN\tnsnames.ora

这样就创建一个软连接,所有的问题都解决了。

后记:其实道理很简单,在pl/sql里面,我们配置的ORACLE_HOME目录,即解压目录。所以,按照默认的惯例,在ORACLE_HOME\NETWORK\ADMIN\tnsnames.ora即是相应的oracle访问信息配置。我们所做的,只不过按照oracle的配置方式将文件放到正确的地方而已。

将jboss 7安装成系统服务

本文自:https://community.jboss.org/wiki/RunningJBossAS7AsAWindowsService

首先从地址 http://www.jboss.org/jbossweb/downloads/jboss-native-2-0-10 下载最新的jboss native文件,将其copy到jboss的bin目录下,原bin目录是不会有native包里的东西的。然后依次进行以下操作即可

  1. 修改service.bat文件,将里面的SVCNAME, SVCDISP and SVCDESC分别修改为自己想要安装成服务的相关信息,即服务名,服务标识以及服务描述信息。
  2. 修改里面的JAVA_OPTS信息配置信息,以配置相应的java内存配置信息,在原service.bat文件中,有默认的-Xrs选项,不过这个选项先不能删掉,以避免某些远程桌面信息会导致jboss停掉的问题。此外,还需要配置比如JAVA_HOME等信息。
  3. 将service.bat中的所有run.bat修改为standalone.bat,在最新的jboss版本中,已不再使用call.bat了。
  4. 将service.bat中关于停止服务的 call shutdown 部分修改为 call jboss-cli.bat –connect command=:shutdown >> shutdown.log 2>&1。因为已经没有shutdown.bat这个文件,如果不修改的话,此调用将直接重启机器。此外,如果配置多个jboss,只需要在后面追加 –controller=host:mport 即可。
  5. 在cmd下运行 service.bat install安装服务,然后在服务中将其启动方式修改为auto即可。

继续阅读“将jboss 7安装成系统服务”

针对WebService使用Service类获取Port类的一个参数问题解释

最后在学习WebService时,看到对于官方的例子是这样写的.

private static final QName PORT_NAME
        = new QName("http://server.hw.demo/", "HelloWorldPort");
        Service service = Service.create(SERVICE_NAME);
        String endpointAddress = "http://localhost:9000/helloWorld";
        service.addPort(PORT_NAME, SOAPBinding.SOAP11HTTP_BINDING, endpointAddress);
        
        HelloWorld hw = service.getPort(HelloWorld.class);

注意看上面的PORT_NAME的定义,是一个QName,其就有namespaceURI和一个name值,而在使用serivce获取port时直接传递了接口名参数。
这样的例子是可以运行的,这就导致了本人在编写参考例子时,直接Copy了相应的代码,但是修改了各项名称,在运行时,即始终运行不起来,相应的错误为

java.net.MalformedURLException: Invalid address. Endpoint address cannot be null.

以上的错误直接让人找不到方向,而实际问题是,service根据所传递的信息,在只传递了接口信息时,会默认构建一个QName的信息,再从service中寻找,如果寻找不到,自然就会产生上面的错误了。
在官方的例子中,它会默认构建HelloWorldPort这样qname去寻找,而在进行service.add时,恰好添加的就是HelloWorldPort这个qname,那么就恰好寻找到了。

而我们的例子,由于做了很多处理,导致默认添加到service的port的name并不是Service.class.getName+Port的组合,那么自然就找不到相应的port了。而正确的做法,其实也很简单,就是在获取port的时候,手动地指定要获取port类的qname,如下所示:

		QName userServicePortQName = new QName("http://cxf.java.study.m_ylf.com/", "abcPort");
		service.addPort(userServicePortQName, SOAPBinding.SOAP11HTTP_BINDING, "http://localhost:8080/userService");
		UserService userService = service.getPort(userServicePortQName, UserService.class);

即在往service时添加什么样的port,那么在获取时就使用什么样的qname。再一步理解,addPort这个方法就可以理解为以键值对的方式往service里追加port,那么在获取的时候自然就要提供相应的key值了。如果不提供,就会使用默认的生成策略创建一个key值,那这个key值与addPort使用的key值不一样的话,自然就会产生上面的错误了。