使用 jstack 对 Java 线程 Dump 分析

Posted by wxianfeng Sun, 01 Dec 2013 03:13:00 GMT

用 httpclient 写了个代理服务, 没跑一会就会出现 504 TimeOut 错误, tomcat 假死现象, 业务日志无任何输出, 切入点使用 jstack 分析线程堆栈

jstack dump 堆栈

查看 Tomcat 进程

➜  photostore git:(photostore_1.0.0) ps aux | grep tomcat
haomiao         40556   0.0  0.6  6352212  51224   ??  S     6:35PM   0:38.59

jstack Dump 进程堆栈:

➜  photostore git:(photostore_1.0.0) jstack -l 40556  > /tmp/ps.log
"http-bio-8080-exec-8" #85 daemon prio=5 os_prio=31 tid=0x00007fb8e18e8000 nid=0x11a13 waiting on condition [0x000000011d20a000]
   java.lang.Thread.State: WAITING (parking)
    at sun.misc.Unsafe.park(Native Method)
    - parking to wait for  <0x000000079e359ad8> (a java.util.concurrent.locks.AbstractQueuedSynchronizer$ConditionObject)
    at java.util.concurrent.locks.LockSupport.park(LockSupport.java:175)
    at java.util.concurrent.locks.AbstractQueuedSynchronizer$ConditionObject.await(AbstractQueuedSynchronizer.java:2039)
    at org.apache.http.pool.PoolEntryFuture.await(PoolEntryFuture.java:138)
    at org.apache.http.pool.AbstractConnPool.getPoolEntryBlocking(AbstractConnPool.java:306)
    at org.apache.http.pool.AbstractConnPool.access$000(AbstractConnPool.java:64)
    at org.apache.http.pool.AbstractConnPool$2.getPoolEntry(AbstractConnPool.java:192)
    at org.apache.http.pool.AbstractConnPool$2.getPoolEntry(AbstractConnPool.java:185)
    at org.apache.http.pool.PoolEntryFuture.get(PoolEntryFuture.java:107)
    at org.apache.http.impl.conn.PoolingHttpClientConnectionManager.leaseConnection(PoolingHttpClientConnectionManager.java:276)
    at org.apache.http.impl.conn.PoolingHttpClientConnectionManager$1.get(PoolingHttpClientConnectionManager.java:263)
    at org.apache.http.impl.execchain.MainClientExec.execute(MainClientExec.java:190)
    at org.apache.http.impl.execchain.ProtocolExec.execute(ProtocolExec.java:184)
    at org.apache.http.impl.execchain.RetryExec.execute(RetryExec.java:88)
    at org.apache.http.impl.execchain.RedirectExec.execute(RedirectExec.java:110)
    at org.apache.http.impl.client.InternalHttpClient.doExecute(InternalHttpClient.java:184)
    at org.apache.http.impl.client.CloseableHttpClient.execute(CloseableHttpClient.java:82)
    at org.apache.http.impl.client.CloseableHttpClient.execute(CloseableHttpClient.java:107)
    at org.apache.http.impl.client.CloseableHttpClient.execute(CloseableHttpClient.java:55)
    at com.yunos.photostore.gatedlaunch.biz.http.HttpReuqestExecutor.doPOST(HttpReuqestExecutor.java:42)
    at com.yunos.photostore.gatedlaunch.web.MyAsyncServlet.service(MyAsyncServlet.java:72)
    at javax.servlet.http.HttpServlet.service(HttpServlet.java:731)
    at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:303)
    at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:208)
    at org.apache.tomcat.websocket.server.WsFilter.doFilter(WsFilter.java:52)
    at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:241)
    at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:208)
    at org.apache.catalina.core.StandardWrapperValve.invoke(StandardWrapperValve.java:220)
    at org.apache.catalina.core.StandardContextValve.invoke(StandardContextValve.java:122)
    at org.apache.catalina.authenticator.AuthenticatorBase.invoke(AuthenticatorBase.java:505)
    at com.taobao.tomcat.valves.ContextLoadFilterValve$FilterChainAdapter.doFilter(ContextLoadFilterValve.java:191)
    at com.taobao.eagleeye.EagleEyeFilter.doFilter(EagleEyeFilter.java:81)
    at com.taobao.tomcat.valves.ContextLoadFilterValve.invoke(ContextLoadFilterValve.java:150)
    at org.apache.catalina.core.StandardHostValve.invoke(StandardHostValve.java:170)
    at org.apache.catalina.valves.ErrorReportValve.invoke(ErrorReportValve.java:103)
    at org.apache.catalina.core.StandardEngineValve.invoke(StandardEngineValve.java:116)
    at org.apache.catalina.connector.CoyoteAdapter.service(CoyoteAdapter.java:429)
    at org.apache.coyote.http11.AbstractHttp11Processor.process(AbstractHttp11Processor.java:1085)
    at org.apache.coyote.AbstractProtocol$AbstractConnectionHandler.process(AbstractProtocol.java:625)
    at org.apache.tomcat.util.net.JIoEndpoint$SocketProcessor.run(JIoEndpoint.java:316)
    - locked <0x000000079cff4ba0> (a org.apache.tomcat.util.net.SocketWrapper)
    at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1142)
    at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:617)
    at org.apache.tomcat.util.threads.TaskThread$WrappingRunnable.run(TaskThread.java:61)
    at java.lang.Thread.run(Thread.java:745)

   Locked ownable synchronizers:
    - <0x000000079cfec500> (a java.util.concurrent.ThreadPoolExecutor$Worker)

可以看到 线程都在 java.lang.Thread.State: WAITING (parking) 再看堆栈是在 httpclient 抛出来的, at org.apache.http.pool.AbstractConnPool.getPoolEntryBlocking(AbstractConnPool.java:306) 可以看出来 取连接的时候, 都被 Block 住了

netstat 看 TCP 连接, 果然存在 50 个 CLOSE_WAIT 的 TCP 连接

➜  photostore-test git:(master) ✗ netstat -a | grep rest.kanbox.com
tcp4       0      0  30.11.194.152.55421    rest.kanbox.com.http   CLOSE_WAIT
tcp4       0      0  30.11.194.152.55418    rest.kanbox.com.http   CLOSE_WAIT
tcp4       0      0  30.11.194.152.55402    rest.kanbox.com.http   CLOSE_WAIT
tcp4       0      0  30.11.194.152.55400    rest.kanbox.com.http   CLOSE_WAIT
tcp4       0      0  30.11.194.152.55398    rest.kanbox.com.http   CLOSE_WAIT
tcp4       0      0  30.11.194.152.55396    rest.kanbox.com.http   CLOSE_WAIT
tcp4       0      0  30.11.194.152.55394    rest.kanbox.com.http   CLOSE_WAIT
tcp4       0      0  30.11.194.152.55392    rest.kanbox.com.http   CLOSE_WAIT
tcp4       0      0  30.11.194.152.55390    rest.kanbox.com.http   CLOSE_WAIT
tcp4       0      0  30.11.194.152.55388    rest.kanbox.com.http   CLOSE_WAIT
tcp4       0      0  30.11.194.152.55386    rest.kanbox.com.http   CLOSE_WAIT
tcp4       0      0  30.11.194.152.55384    rest.kanbox.com.http   CLOSE_WAIT
tcp4       0      0  30.11.194.152.55382    rest.kanbox.com.http   CLOSE_WAIT
tcp4       0      0  30.11.194.152.55380    rest.kanbox.com.http   CLOSE_WAIT
tcp4       0      0  30.11.194.152.55378    rest.kanbox.com.http   CLOSE_WAIT
tcp4       0      0  30.11.194.152.55376    rest.kanbox.com.http   CLOSE_WAIT
tcp4       0      0  30.11.194.152.55374    rest.kanbox.com.http   CLOSE_WAIT
tcp4       0      0  30.11.194.152.55372    rest.kanbox.com.http   CLOSE_WAIT
tcp4       0      0  30.11.194.152.55370    rest.kanbox.com.http   CLOSE_WAIT
tcp4       0      0  30.11.194.152.55368    rest.kanbox.com.http   CLOSE_WAIT
tcp4       0      0  30.11.194.152.55366    rest.kanbox.com.http   CLOSE_WAIT
tcp4       0      0  30.11.194.152.55364    rest.kanbox.com.http   CLOSE_WAIT
tcp4       0      0  30.11.194.152.55362    rest.kanbox.com.http   CLOSE_WAIT
tcp4       0      0  30.11.194.152.55360    rest.kanbox.com.http   CLOSE_WAIT
tcp4       0      0  30.11.194.152.55357    rest.kanbox.com.http   CLOSE_WAIT
tcp4       0      0  30.11.194.152.55338    rest.kanbox.com.http   CLOSE_WAIT
tcp4       0      0  30.11.194.152.55336    rest.kanbox.com.http   CLOSE_WAIT
tcp4       0      0  30.11.194.152.55127    rest.kanbox.com.http   CLOSE_WAIT
tcp4       0      0  30.11.194.152.55090    rest.kanbox.com.http   CLOSE_WAIT
tcp4       0      0  30.11.194.152.55089    rest.kanbox.com.http   CLOSE_WAIT
tcp4       0      0  30.11.194.152.55088    rest.kanbox.com.http   CLOSE_WAIT
tcp4       0      0  30.11.194.152.55087    rest.kanbox.com.http   CLOSE_WAIT
tcp4       0      0  30.11.194.152.55086    rest.kanbox.com.http   CLOSE_WAIT
tcp4       0      0  30.11.194.152.55085    rest.kanbox.com.http   CLOSE_WAIT
tcp4       0      0  30.11.194.152.55084    rest.kanbox.com.http   CLOSE_WAIT
tcp4       0      0  30.11.194.152.55081    rest.kanbox.com.http   CLOSE_WAIT
tcp4       0      0  30.11.194.152.55079    rest.kanbox.com.http   CLOSE_WAIT
tcp4       0      0  30.11.194.152.55075    rest.kanbox.com.http   CLOSE_WAIT
tcp4       0      0  30.11.194.152.55058    rest.kanbox.com.http   CLOSE_WAIT
tcp4       0      0  30.11.194.152.55050    rest.kanbox.com.http   CLOSE_WAIT
tcp4       0      0  30.11.194.152.55025    rest.kanbox.com.http   CLOSE_WAIT
tcp4       0      0  30.11.194.152.55021    rest.kanbox.com.http   CLOSE_WAIT
tcp4       0      0  30.11.194.152.55020    rest.kanbox.com.http   CLOSE_WAIT
tcp4       0      0  30.11.194.152.55019    rest.kanbox.com.http   CLOSE_WAIT
tcp4       0      0  30.11.194.152.55017    rest.kanbox.com.http   CLOSE_WAIT
tcp4       0      0  30.11.194.152.55016    rest.kanbox.com.http   CLOSE_WAIT
tcp4       0      0  30.11.194.152.55012    rest.kanbox.com.http   CLOSE_WAIT
tcp4       0      0  30.11.194.152.54985    rest.kanbox.com.http   CLOSE_WAIT
tcp4       0      0  30.11.194.152.54981    rest.kanbox.com.http   CLOSE_WAIT
tcp4       0      0  30.11.194.152.54980    rest.kanbox.com.http   CLOSE_WAIT

设置的 连接池 50 个, 所以只能处理 50 次请求就会出现 504 timeout 了, 因为连接池中取不到链接了。

CLOSE_WAIT 说明被动关闭了, 即对方关闭了连接, 自己没有关闭

解决办法

主动关闭连接: HttpClient 使用我们常用的InputStream.close()来确认连接关闭, httpclient 拿到 response, response 读取完了, 然后关闭流

public static void prepareForResponse(HttpServletResponse response, HttpResponse result) throws IOException {
    response.setStatus(result.getStatusLine().getStatusCode());
    response.setHeader("Content-Type","text/html;charset=UTF-8");
    InputStream inputStream = result.getEntity().getContent();
    OutputStream outputStream = response.getOutputStream();
    byte[] buffer = new byte[1024];
    int len ;
    while ((len = inputStream.read(buffer)) != -1) {
        outputStream.write(buffer, 0, len);
    }
    inputStream.close();
}

这个 case 告诉我们, Java 服务遇到假死的情况, 可以使用 jstack 神器。


ngx_lua 连接mysql

Posted by wxianfeng Sat, 16 Nov 2013 04:02:00 GMT

openresty 安装后, 默认包含 lua-resty-mysql, 我就是用的这个连接的 mysql, 很好用.

当一个 request 进来过后, 一般这么用:

local mysql = require "resty.mysql"
local db = mysql:new()
local cfg = { ... }
db:connect(cfg)
local res = db:query("select * from users")
ngx.say(json.encode(res))

但是如果我 require 的一个文件中也需要连接 mysql, 怎么办?

刚开始我这么用的:

a.lua

local Email = require("email")
local item = Email:get_item(5)

email.lua

local mysql = require("resty.mysql")
local db = mysql:new
db:connect(cfg)

function Email:get_item(id)
  local res = db:query("select * from emails where id = " .. id)
  local item = res[1]
  return item
end

一个 request 过来后, 直接报错:

> 2013/11/14 19:30:09 [error] 5290#0: *425 lua entry thread aborted: runtime
> error: attempt to yield across C-call boundary

出现这个原因是因为 require 的文件中, 不能直接有 mysql io 操作.

看下 春哥 的回复:

Hello!

2013/11/14 wxianfeng:
> 最新测试, 发现同时在两个lua文件中 db:connect(cfg) 不成功, 报下面错误:
>
> 2013/11/14 19:30:09 [error] 5290#0: *425 lua entry thread aborted: runtime
> error: attempt to yield across C-call boundary
>

这个错误是说你使用的某个 C 函数实现的 Lua 原语(比如 require 和 loadfile 之类)直接触发了 resty.mysql
的 I/O 操作(例如 connect 之类)。你应当避免让 require 和 loadfile 等操作直接触发 mysql 的 I/O
操作(比如把 mysql 相关的操作都放在你自己的 Lua 函数里,而不是放在 Lua 模块文件(.lua 文件)的顶层上)。

最后我把 email.lua 中连接 mysql 的操作, 放到一个函数中 解决.

local mysql = require("resty.mysql")
local db = mysql:new


local function connect_db()
  db:connect(cfg)
end

function Email:get_item(id)
  connect_db()
  local res = db:query("select * from emails where id = " .. id)
  local item = res[1]
  return item
end

ngx_lua log调试

Posted by wxianfeng Tue, 10 Sep 2013 02:46:00 GMT

ngx_lua 如何调试, 查看出错信息,和让修改的lua代码不重启nginx生效?
我在用 ngx_lua + lua-resty-mysql 提供json API, 每次改sql, 都需要 reload nginx , 非常麻烦. 解决办法如下:

打印输出log

可以使用 inspect.lua, 这样 print 就可以打印 lua 的 table 了, table 内的值也可以全部打印出来

local inspect = require "inspect"
local a = { retCode =  1 }
print(inspect(a))

查看输出 或者错误

查看 nginx error.log 即可以看到 print 输出, 或者出错的信息, 注意配置 error_log 指令的时候, 添加上 debug

error_log logs/error.log debug;

lua 代码修改后生效

使用 lua_code_cache off 即可, 另外注意只有使用 content_by_lua_file 才会生效.

http {
  lua_code_cache off;
}

location ~* /(\d+-.*)/api/orgunits/load_all(.*) {
   default_type 'application/json;charset=utf-8';
   content_by_lua_file /data/projects/xxx/current/lua/controller/load_data.lua;
}

Node.js 汉字转拼音

Posted by wxianfeng Fri, 30 Aug 2013 17:06:00 GMT

之前写了一个 ruby 汉字转拼音的 gem , 项目需要, 把它翻译成了 nodejs module.

安装

npm install hanzi_to_pinyin

使用

var hanzi_to_pinyin = require("hanzi_to_pinyin");
hanzi_to_pinyin.hanzi_to_pinyin("测试");

命令行bin

>hanzi_to_pinyin "测试"
=>ce;shi

例子

assert.equal(hanzi_to_pinyin.hanzi_to_pinyin("ab你好c"),'ab;ni;hao;c');
assert.equal(hanzi_to_pinyin.hanzi_to_pinyin("我们"),"wo;men");
assert.equal(hanzi_to_pinyin.hanzi_to_pinyin("yyf"),"yyf");
assert.equal(hanzi_to_pinyin.hanzi_to_pinyin("拗Smith"),"ao,niu;Smith");
assert.equal(hanzi_to_pinyin.hanzi_to_pinyin("测试1"),"ce;shi;1");
assert.equal(hanzi_to_pinyin.hanzi_to_pinyin("测_试-"),"ce;_;shi;-");
assert.equal(hanzi_to_pinyin.hanzi_to_pinyin("2"),"2");

源码

nodejs

ruby


Lua 时间处理

Posted by wxianfeng Fri, 28 Jun 2013 03:11:00 GMT

得到当前时间

> print(os.date())
Wed Jun 26 10:27:15 2013

得到当前时间戳

> print(os.time())
1372155588

时间戳转时间

> print(os.date("%c",1372153800))
Tue Jun 25 17:50:00 2013

时间转时间戳

-- Assuming a date pattern like: yyyy-mm-dd hh:mm:ss
local pattern = "(%d+)-(%d+)-(%d+) (%d+):(%d+):(%d+)"
local timeToConvert = "2011-01-01 01:30:33"
local runyear, runmonth, runday, runhour, runminute, runseconds = timeToConvert:match(pattern)

local convertedTimestamp = os.time({year = runyear, month = runmonth, day = runday, hour = runhour, min = runminute, sec = runseconds})

计算 30 分钟前时间, 可以使用 秒计算出

> print(os.time() - 30*60)
1372153800
> print(os.date("%c",1372153800))
Tue Jun 25 17:50:00 2013

PhoneGap IOS 调试

Posted by wxianfeng Tue, 14 May 2013 04:07:00 GMT

phonegap ios 调试技巧, 目前几个我用过的:

1, chrome 有 ripple 模拟器插件, 但是会出现phonegap js api 不解释, 需要屏蔽掉

2, 利用 http://debug.phonegap.com/ 远程调试, 但是延时严重

3, 把 wenire 装到本地进行调试, 没有尝试.

4, 如果利用 浏览器调试, 可以判断 window.device 是否存在来屏蔽掉 phonegap js api

5, 模拟器和真机调试可以使用 safari web inspector

打开 safari -> preference -> advanced -> develop

最后附张模拟器调试的图, 可以实时调css, 还可以打js断点.

总结下来就是, 前期开发使用 浏览器, 后期细节调试使用 xcode 模拟器 或者真机 + safari web inspector 来调整.


Rails csrf_token 和 Session CookieStore 原理

Posted by wxianfeng Fri, 01 Mar 2013 22:59:00 GMT

环境: ruby 1.8.7 + rails 2.3.x, ruby 1.9.2 + rails 3.x

前段时间处理手机客户端post请求问题, 经常遇到csrf token 问题, 另外web上也经常遇到和session相关的问题, 不深究下去, 很多东西云里雾里, 于是把rails源码csrf_token, 和 session cookiestore 相关的代码研究了下.

csrf_token 原理

这个相信做rails的, 没有不知道的,是rails framework中为了防止 XSS攻击的, 可是你知道它的原理吗?
好, 顺着代码跟进去, 这是 rails 3.x 的源码.
入口就是 ApplicationController 中的 protect_from_forgery

    # File actionpack/lib/action_controller/metal/request_forgery_protection.rb, line 85
       def protect_from_forgery(options = {})
         self.request_forgery_protection_token ||= :authenticity_token
         prepend_before_filter :verify_authenticity_token, options
       end

再找到 verify_authenticity_token

       def verify_authenticity_token
         verified_request? || handle_unverified_request
       end

再找到 verified_request?

发现判断合法的请求方法:

1, 跳过不验证的
2, GET 请求
3, csrf_token 和参数中 authenticity_token 值相同的
4, http header 中 X-CSRF-Token 和 csrf_token 的值相同的

不合法请求会 reset_session

       def handle_unverified_request
         reset_session
       end

来看看 rails 2.x 的, 为什么说 rails 2.x 的,和 rails 3.x 不一样, 影响也很大.

合法的请求:

1, 跳过检查的
2, GET 请求
3, ajax 请求
4, 不是 html 格式请求, 例如 json, xml 格式请求都是合法的
5, csrf_token 值和 参数中 authenticity_token 值相同的

不合法的请求会 raise error

       def verify_authenticity_token
         verified_request? || raise(ActionController::InvalidAuthenticityToken)
       end

上面的处理都有漏洞, 来看看

rails 3.x 的, 假如用户登录是 post, 登录前还没有 session ,此时会 reset_ssession,因为本来就没有登录后的session,reset_session后,后面的代码继续执行, 假如用户知道用户用户名,密码,利用http client 工具就可以成功获得登录后的session, 虽然 csrf 会验证失败, 所以可以自己打个patch使用 rails 2.x 的方式, 直接 raise

rails2.x 的当请求格式不是 html,是 json 就可以成功跳过 csrf 验证, 例如我这个更新redmine的脚本就是利用这个漏洞实现的.

https://gist.github.com/wxianfeng/5070599

那么 csrf_token 的值又是存在什么地方的呢, 在 session[:_csrf_token],rails 默认session是 cookie store, 这就涉及到cookiestore原理了.

关于 csrf_token 还有一个需要注意的地方, 在 test env 下是不需要 csrf_token 的, 顺着 csrf_meta_tag 跟进去可以看到.

Rails Session CookieStore 原理

在rails后端调试下 session, 打印出来的结果是一个hash, 以github 为例, 先反向得到 session 数据, 用firebug可以看到github的cookie中有一个 _gh_session, 如下:

_gh_sess=BAh7CjoPc2Vzc2lvbl9pZCIlMjM1OGMwZjFhYmU2MTQ0MGRlYWUzYWVhODVhM2U2MTk6EF9jc3JmX3Rva2VuSSIxcHNLWEFoYittaXVVVnZXU3BxMDBJaE52Z0QvQ0kyYjg1cU5pNTJMU2R6TT0GOgZFRjoJdXNlcmkD2IsBOhBmaW5nZXJwcmludCIlZGFmNjBhOGFlYTJlZWE3YThjNWY1OGRmMzg2YzhhNWQ6DGNvbnRleHRJIgYvBjsHRg%3D%3D--0320c02623b8a27a66bbbcd38d095511c459e1f3;

取出 — 前面的部分, 假设为 data

::Marshal.load ::Base64.decode64(data) 后会得到一个hash, 这个就是后端的 session数据

ruby-1.9.2-p290 :016 >   data = "BAh7CjoPc2Vzc2lvbl9pZCIlMjM1OGMwZjFhYmU2MTQ0MGRlYWUzYWVhODVhM2U2MTk6EF9jc3JmX3Rva2VuSSIxcHNLWEFoYittaXVVVnZXU3BxMDBJaE52Z0QvQ0kyYjg1cU5pNTJMU2R6TT0GOgZFRjoJdXNlcmkD2IsBOhBmaW5nZXJwcmludCIlZGFmNjBhOGFlYTJlZWE3YThjNWY1OGRmMzg2YzhhNWQ6DGNvbnRleHRJIgYvBjsHRg%3D%3D"
 => "BAh7CjoPc2Vzc2lvbl9pZCIlMjM1OGMwZjFhYmU2MTQ0MGRlYWUzYWVhODVhM2U2MTk6EF9jc3JmX3Rva2VuSSIxcHNLWEFoYittaXVVVnZXU3BxMDBJaE52Z0QvQ0kyYjg1cU5pNTJMU2R6TT0GOgZFRjoJdXNlcmkD2IsBOhBmaW5nZXJwcmludCIlZGFmNjBhOGFlYTJlZWE3YThjNWY1OGRmMzg2YzhhNWQ6DGNvbnRleHRJIgYvBjsHRg%3D%3D" 
ruby-1.9.2-p290 :017 > ::Marshal.load ::Base64.decode64(data)                                                          
=> {:session_id=>"2358c0f1abe61440deae3aea85a3e619", :_csrf_token=>"psKXAhb+miuUVvWSpq00IhNvgD/CI2b85qNi52LSdzM=", :user=>101336, :fingerprint=>"daf60a8aea2eea7a8c5f58df386c8a5d", :context=>"/"}

再来正向生成

data = ::Base64.encode64 Marshal.dump(h)

那—后面的 digest 是怎么生成的, 和rails中 secrect 合起来加密生成的, 这样别人就不能伪造cookie了,术语叫 cookie 签名.

大概生成算法是这样:

session = {"_csrf_token"="xxxxx","user_id"=>4}
session_data = ::Base64.encode64 Marshal.dump(session)
session_data = "#{session_data}--#{generate_hmac(session_data, @secrets.first)}"

def generate_hmac(data, secret)
     OpenSSL::HMAC.hexdigest(OpenSSL::Digest::SHA1.new, secret, data)
end

源码位置:

/Users/wangxianfeng/.rvm/gems/ruby-1.9.2-p290/gems/rack-1.4.3/lib/rack/session/cookie.rb
/home/wxianfeng/.rvm/gems/ruby-1.9.2-p320/gems/activesupport-3.2.2/lib/active_support/message_verifier.rb

总结起来一句话, rails 的session cookiestore 是存在浏览器的cookie中的, 由 http 协议的 headers 带到后端, 后端解包出来的.

OVER, 是不是没想到啊?


SAS1068E RAID卡创建RAID1 磁盘阵列

Posted by wxianfeng Tue, 19 Feb 2013 17:15:00 GMT

为生产服务器搭建RAID1磁盘阵列,一共四块 1T 硬盘, 其中两块做RAID 1, 作为数据盘, 一块为操作系统盘, 一块为 备份盘.

什么是 RAID1 ?

RAID 1 是两块物理硬盘作为一个逻辑盘, 可以有效的备份, 两块盘的数据一摸一样, 可以提高读的性能, 写性能变化不大.

搭建过程:

1, 开机按 ctrl + c 进入创建界面, 回车进入 adater properties

2, 选择 RAID Properties

3, 选择 create IM volume 创建 RAID 1

4, 选择两块硬盘作为 RAID 1盘, 按空格 选中, 按 c 创建

5, 保存被退出

6, 最后查看到RAID 1 列表

下图的 sdc 就是 RAID 1的逻辑盘

操作系统安装在sda 盘

另外的 sdb 盘就是备份盘了.

关于 RAID 网上看到的很形象的图, 呵呵.