月度归档:2013年03月

异步Servlet (Servlet 3.0)的session机制

先从同步阻塞Servlet应用中的session机制实现说起,Java规范中定义session的类是HttpSession,通过HttpServletRequest.getSession()可以得到。用户第一次访问应用后,应用会在HttpServletResponse中设置Cookie,Cookie名称为JSESSIONID,值为一串随机数。下次用户再访问应用时,服务器就能通过Cookie中的JSESSIONID从服务器维护的Session映射表中拿到该用户的HttpSession。HttpSession中包含Map结构,可存储多种与当前用户相关的状态,HTTP服务器就是通过这种手段实现状态存储。所以,同一个浏览器(与是否多个tab页无关)打开的页面在服务器端只有一个HttpSession,通过HttpSession.getId()可获得该Session的ID,其值等于JSESSIONID。这种通过Session保存用户状态的方法在同步Servlet中没什么问题。

但是在异步Servlet (Servlet 3.0)中,由于HttpServletResponse通常情况下都会被挂起一段时间后再返回,会导致在第一个HttpServletResponse返回之前,所有访问应用的请求都会被当做新用户来对待,此时,服务器端调用HttpServletRequest.getSession(false)总会返回null,如果掉用HttpServletRequest.getSession()或HttpServletRequest.getSession(true),则每次都会返回一个新的HttpSession对象。这样必然会对用户状态的存储带来影响。有一个解决方案是在服务器端通过response.sendRedirect()来强制立即返回一次。sendRedirect是向浏览器返回302 Found,告诉浏览器访问的URL临时变更,新地址在响应头的Location中,让浏览器向新地址再请求一次,同时,JSESSIONID也被放置在Set-Cookie头中一同返回。此时浏览器的Cookie中就有JSESSIONID信息,随即发出的HTTP请求会携带JSESSIONID,服务器端程序通过JSESSIONID可匹配到HttpSession。这个过程中服务器端要做的就是将新地址通过sendRedirect告诉浏览器,Java代码如下:

1
2
3
4
5
6
if (request.getSession(false) == null) {
    request.getSession(true);
    response.sendRedirect(request.getRequestURI() + "?" 
        + request.getQueryString());
    return;
}

以上过程可通过Tomcat+curl模拟,用Tomcat7.0.35跑一个异步Servlet接口,客户端用curl测试,试验结果如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
$ curl -v  http://localhost/async?timestamp=136376159601
* About to connect() to localhost port 80 (#0)
*   Trying 127.0.0.1...
* connected
* Connected to localhost (127.0.0.1) port 80 (#0)
> GET /async?timestamp=136376159601 HTTP/1.1
> User-Agent: curl/7.24.0 (x86_64-apple-darwin12.0) libcurl/7.24.0
> Host: localhost
> Accept: */*
> 
< HTTP/1.1 302 Found
< Server: Apache-Coyote/1.1
< Set-Cookie: JSESSIONID=7D87ED786F18AE59F485D18F6F2094E3; Path=/; HttpOnly
< Location: http://localhost/async?timestamp=136376159601
< Content-Length: 0
< Date: Tue, 26 Mar 2013 11:57:59 GMT
< 
* Connection #0 to host localhost left intact
* Closing connection #0

此方法能解决异步Servlet开始阶段同一个用户请求Session总是不同的问题,其缺点是用户在第一次访问异步Servlet时,总会被强制重定向一次。不过相比它带来的好处,这点缺陷实在是可以忽略的。

--EOF--

『我的奋斗』

每天浸染互联网的人总会听说过一些关于罗永浩的只言片语:创办老罗英语培训学校,创办牛博网,西门子冰箱维权,与方舟子的骂战……在媒体和网友的眼中,老罗一直是个彪悍、幽默、特立独行的形象,『我的奋斗』汇编老罗演讲稿、自述和语录,记录老罗“成功”之前的故事……老罗是个有故事的人,不论故事是否真的发生过,从他口中说出来,就是他的故事。有知名公众人物曾评价他是“一个价值观完全正确的人”,是否价值观完全正确,评判标准在于个人。以下就是老罗输出的几点价值观:

1. 关于阅读
老罗自小闲书读得多,这让他跟身边学习成绩好的优秀孩子交往起来让自己更有自信。因为这份自信,小学三年级时,他在课堂上多次指出老师的错误,让老师觉得很不爽,后来她派她的高中儿子来打他,⊙﹏⊙b,这一段我觉得都能上糗百了。阅读很重要,老罗说他辍学在家读的三年闲书收获比在学校里大得多。即使是成功学的书,虽然没有思想没有内容没有营养,但作为精神鸦片的作用可以在奋斗过程中坚持不下去时拿来随手翻翻提提神。

2. 关于教育
学校教育一直倡导“知识改变命运”,老罗也相信“知识改变命运”,然而与老罗价值观冲突的是,现行教育体制实际产出的是“学历改变命运”。教育体制外的老罗,体制内的学生,都在读鲁迅,不同的是老罗读『鲁迅全集』,学生读鲁迅的某一篇文章的节选,读完之后还要回答类似“节选中第二自然段为什么鲁迅要讲那么一句话?”之类的问题,这些可能连鲁迅自己都不知道的答案,偏偏教委知道,是否知道答案还关系到升学……这样的场景,我想是所有从小在课堂上学过鲁迅、学过冰心、学过朱自清的人都会有的体会吧。

3. 关于中医
老罗是坚定的中医反对者。他认为中医既然一时半会儿否定不了,那么更应该从有趣的科普文章入手,即使无法马上改变世界,至少留下火种,留下希望,并引用普朗克一段话“一个新的科学真理取得胜利并不是通过让它的反对者们信服并看到真理的光明,而是通过这些反对者们最终死去,而新的一代一开始就熟悉它并不断地成长起来的”。会思考的人即使吃着中药长大也会有意识到中医是伪科学的一天。

4. 关于逆境
面对逆境,老罗教人“不要伤心,不要难过、不要沮丧、甚至不要控诉、不要愤怒、不要抗议,只管埋头默默擦亮你的武器,准备下一次的战斗”,什么叫埋头默默擦亮武器?有一个东汉光武帝刘秀的故事,当时刘秀打了败仗,夜里巡房时士兵们有哭哭啼啼的,有写家书写情书的,有醉酒的……只有一个将军拿着一块布在不停地擦着自己的武器和盔甲,脸上表情不悲伤不沮丧不愤怒不亢奋……后来,这个将军成为东汉的开国名将,他叫吴汉,“云台二十八将”中排第二。

5. 关于独立思考
独立思考的标准是什么? 就是“怀疑一切过去没经自己思考判断就接受了的想法”。在大家都批判这个社会笑贫不笑娼时,独立思考的人会反问"为什么妓女就该被笑?妓女比穷人差在哪里?"。

--EOF--

git reflog

今天git提交代码的时候,因为提交了一个错误的文件,于是蛋疼地用刚学到的git reset --hard命令来取消提交。按我的理解,git reset只是移除仓库中的commit记录,使所有相关文件回到暂存区,也就是git add之后的状态。结果就悲剧了。

还好有个git-reflog命令,可以详细记录仓库中各个分支的状态变更情况,比如checkout、merge、reset、cherry-pick,以及最重要的commit操作。从git-reflog命令输出的列表中可以看见每次操作的commit-id,如:

1
2
3
4
5
MacBook-Pro:pf fengchj$ git reflog
ac86a08 HEAD@{0}: reset: moving to ac86a080cc0859662e727de625fc414a211ba6c2
92f3986 HEAD@{1}: commit: update configuration. 
ac86a08 HEAD@{2}: commit: misc: 解决接口中文乱码问题。
1ccb4a3 HEAD@{3}: commit: misc: 修改获取列表接口配置(兼容状态推送)。

从上面可以看出,因为执行了git reset --hard ac86a080cc0859662e727de625fc414a211ba6c2导致当前分支回到了“ac86a08 HEAD@{2}: commit: misc: 解决接口中文乱码问题”提交后的状态,也就是说丢失了“92f3986 HEAD@{1}: commit: update configuration. ”这次提交的所有信息(git log中看不到)。

git有多种机制保障只要知道丢失的commit-id,就可以还原因误操作丢失的commit记录。

例如,执行git reset --hard 92f3986可以将分支强制回复到92f3986提交后的状态。也可以用『git cherry-pick』一文提到的git-cherry-pick命令来还原:git cherry-pick 92f3986。此外,还可以用merge命令来还原:git merge 92f3986。

所以只要有了git-reflog命令,就不用怕因误操作而丢失commit记录。另外,如果不进行额外配置,git-reflog默认保存30天的操作记录。

References:
[1] reflog, your safety net. http://gitready.com/intermediate/2009/02/09/reflog-your-safety-net.html .
[2] git-reflog(1) Manual Page. https://www.kernel.org/pub/software/scm/git/docs/git-reflog.html .

--EOF--