1. 程式人生 > >使用Jmeter登入WordPress的問題(Cookie管理)

使用Jmeter登入WordPress的問題(Cookie管理)

1. 背景簡介
最近在開始對Xen/KVM的巢狀虛擬化(nested virtualization)做一些效能測試並收集一些效能資料,以便證明各種特性(如virtual EPT、VMCS shadowing等)在巢狀虛擬化中帶來的效能提高。其中,我們還考慮到當前Web應用伺服器是使用虛擬化技術的一個重要方面,所以專門設計了對Web應用的效能測試。對於我們倆這樣做虛擬化團隊來說,平時是沒有什麼特別重要的線上Web服務的,我就開始找一些Web測試的benchmark,最開始找到了的SPECweb2009,但經過簡單的分析和試用,發現它配置和使用真的太複雜了(對於非專職Web效能測試來說),而且主要是它衡量是一定的電力功率的情況下支撐的同時線上使用者數,這個指標一般來說並不是多數Web開發者關心的(從以前的Web測試經驗來說,一般最關心的是響應時間和TPS),所以我還是覺得不用specweb了。由於我的部落格是使用WordPress,對它還是比較熟悉了,所以我選擇最新的WordPress來作為Web效能測試的伺服器端程式吧。 我初步選擇了主頁瀏覽和使用者登入這兩個基本場景來做分析。當然,我還是繼續選用了曾經非常熟悉的JMeter來作為Web效能測試工具,最新版本是JMeter 2.9(離我曾經常用的2.3版本已經兩年多了,不過這次還藉機會根據遇到的問題看了點JMeter的原始碼)。


2. 問題描述:


在JMeter中做WordPress的使用者登入場景時,卻遇到了問題:在JMeter中總是登入不成功,打不開wp-admin頁面,而通過Firefox瀏覽器都是可以正常登入。


3. 初步分析
根據經驗,是需要在JMeter中新增Cookie Manager來管理Cookie,這個是必須的,不過新增Cookie Manager後依然不能登入Wordpress。
依然懷疑是cookie問題,網上搜索找到了這篇:使用Jmeter登入WordPress
這裡描述的問題,和我遇到的類似,也可以使用裡面提到的解決方法(不過這不是太好的方法,還不是問題的本質)。


4. 深入分析
在找到上面的臨時解決方法後,確實可以暫時工作了,不過我還是在想為什麼第二次請求wp-admin頁面時就缺少cookie呢,而且需要Cookie Manager顯式地新增cookie。
通過JMeter的檢視結果樹功能,分別分析其請求和相應過程,其中登入的Post請求和相應的部分內容如下:

POST http://192.168.52.11/wp-login.php
POST data:
log=admin&pwd=123456&wp-submit=Log+In&redirect_to=http%3A%2F%2F192.168.52.11%2Fwp-admin%2F&testcookie=1

Response headers:
HTTP/1.1 302 Found
Date: Sat, 20 Apr 2013 17:14:45 GMT
Server: Apache/2.2.15 (Red Hat)
X-Powered-By: PHP/5.3.3
Expires: Wed, 11 Jan 1984 05:00:00 GMT
Cache-Control: no-cache, must-revalidate, max-age=0
Pragma: no-cache
Set-Cookie: wordpress_test_cookie=WP+Cookie+check; path=/
X-Frame-Options: SAMEORIGIN
Set-Cookie:
wordpress_81eeec675c437946cd44da8ab3073e4b=admin%7C1366650885%7Ceaad49425daa629073c9d2d3862e0afb;
path=/wp-content/plugins; httponly
Set-Cookie:
wordpress_81eeec675c437946cd44da8ab3073e4b=admin%7C1366650885%7Ceaad49425daa629073c9d2d3862e0afb;
path=/wp-admin; httponly
Set-Cookie:
wordpress_logged_in_81eeec675c437946cd44da8ab3073e4b=admin%7C1366650885%7C23f9129257590e96bcfd97c8aae6f622;
path=/; httponly
Location: http://192.168.52.11/wp-admin/
Content-Length: 0
Connection: close
Content-Type: text/html; charset=UTF-8


可以看到,POST登入資訊之後,WordPress返回關於/wp-admin路徑的Cookie的,但是自動跳轉到/wp-admin時並沒有把這裡的“wordpress_81eeec675c43…”這樣的Cookie傳送出去。所以,我就開始懷疑為啥JMeter沒有正確地傳送Cookie,花了一個小時也沒找到JMeter的Cookie Manager在使用Cookie的任何bug。我忽然想起來,或許是JMeter收到上面的response header中的Set-Cookie,但是沒有正確地儲存下來。這是我查看了JMeter執行的日誌jmeter.log,發現有如下資訊:

2013/04/21 01:04:32 INFO - jmeter.protocol.http.control.CookieManager: Settings: Delete null: true Check: true Allow variable: true Save: false Prefix: COOKIE_

2013/04/21 01:04:35 WARN - jmeter.protocol.http.control.HC3CookieHandler: Not storing invalid cookie: for URL http://192.168.52.11/wp-login.php (Illegal path attribute "/wp-content/plugins". Path of origin: "/wp-login.php")
2013/04/21 01:04:35 WARN - jmeter.protocol.http.control.HC3CookieHandler: Not storing invalid cookie: for URL http://192.168.52.11/wp-login.php (Illegal path attribute "/wp-admin". Path of origin: "/wp-login.php")

這就是證實了我的判斷,是HttpClient認為從/wp-login.php發來的關於/wp-admin路徑的cookie是無效的(invalid),所以沒有儲存下來的。
然後根據這裡的資訊,結合JMeter和HttpClient的程式碼,可以看到如下有用的邏輯。
首先,設定是否檢查Cookie的有效性,程式碼在:
apache-jmeter-2.9/src/protocol/http/org/apache/jmeter/protocol/http/control
CookieManager.java

1
2
3
4
5
6
7
public class CookieManager 
{
...
    private static final boolean CHECK_COOKIES =
        JMeterUtils.getPropDefault("CookieManager.check.cookies", true);// $NON-NLS-1$
...
}

然後在 HC3CookieHandler.java 中呼叫CookieSpec類的validate方法來檢查Cookie,如下:

1
2
3
4
5
6
7
8
public void addCookieFromHeader(CookieManager cookieManager,
                boolean checkCookies,String cookieHeader, URL url) {
...
                if (checkCookies) {
                    cookieSpec.validate(host, port, path, isSecure, cookie);
                }
...
}

最後在HttpClient中的檢查Cookie的validate()方法的判斷邏輯如下:
src/java/org/apache/commons/httpclient/cookie/CookieSpecBase.java
(線上程式碼瀏覽:http://hc.apache.org/httpclient-3.x/xref/org/apache/commons/httpclient/cookie/CookieSpecBase.html)

1
2
3
4
5
6
7
8
9
10
public void validate(String host, int port, String path, 
        boolean secure, final Cookie cookie) {
..
         if (!path.startsWith(cookie.getPath())) {
             throw new MalformedCookieException(
                 "Illegal path attribute \"" + cookie.getPath() 
                 + "\". Path of origin: \"" + path + "\"");
         }
...
}

這裡的原因是,設定Cookie的response對應的request路徑是/wp-login.php,而它卻設定了/wp-admin(和/wp-content/plugins)路徑的Cookie,JMeter不接受。
另外,進一步分析為什麼HttpClient有這樣的檢查呢,原因是HTTP協議的RFC2109中關於Cookie檢查的規定中指出了哪些型別的Cookie是應該被拒絕的。

To prevent possible security or privacy violations, a user agent
rejects a cookie (shall not store its information) if any of the
following is true:

* The value for the Path attribute is not a prefix of the request-
URI.

* The value for the Domain attribute contains no embedded dots or
does not start with a dot.

* The value for the request-host does not domain-match the Domain
attribute.

* The request-host is a FQDN (not IP address) and has the form HD,
where D is the value of the Domain attribute, and H is a string
that contains one or more dots.

RFC2109和RFC2965(較新)都是值得參考的,見:http://www.ietf.org/rfc/rfc2109.txt 和 http://www.ietf.org/rfc/rfc2965.txt
根據HTTP協議的規定,所以HttpClient提供了validate方法來檢測Cookie,JMeter也是呼叫HttpClient而已(話說,支援HC3和HC4兩種版本的HttpClient)。

5. 解決方案
當我把問題分析清楚之後,那麼解決方案就是那麼顯而易見了,有如下兩種思路(第一種比較方便):
1. 在 bin/user.properties 或 bin/jmeter.properties 新增(或修改)使其不檢查cookie,配置項為:
CookieManager.check.cookies=false

2. 可以考慮修改WordPress的response中對Cookie的設定方法(因為它目前這樣做並不太規範,儘管IE、Firefox、Chrome等瀏覽器一般都能正常使用Cookie),不過這個方法比較麻煩的(作者也尚未實踐)。
另外,在分析和解決JMeter的問題時,可以設定log的等級為DEBUG,在bin/jmeter.properties 中設定:
log_level.jmeter=DEBUG
(其預設值為INFO, 會列印:Jmeter的 FATAL_ERROR, ERROR, WARN, INFO 這4個級別的log;全部的log級別為:FATAL_ERROR, ERROR, WARN, INFO, and DEBUG。)也可以在JMeter的GUI中選中“Help”->“Enable debug”選項。
設定“CookieManager.check.cookies=false”後,JMeter就可以登入WordPress了,如果設定了DEBUG,則可以在jmeter.log中看到如下的資訊:

2013/04/21 01:23:28 INFO - jmeter.protocol.http.control.CookieManager: Settings: Delete null: true Check: false Allow variable: true Save: false Prefix: COOKIE_

2013/04/21 01:23:30 DEBUG - jmeter.protocol.http.control.HC3CookieHandler: Received Cookie: wordpress_81eeec675c437946cd44da8ab3073e4b=admin%7C1366650885%7Ceaad49425daa629073c9d2d3862e0afb; path=/wp-content/plugins; httponly From: http://192.168.52.11/wp-login.php
2013/04/21 01:23:30 DEBUG - jmeter.protocol.http.control.CookieManager: Add cookie to store 192.168.52.11 TRUE /wp-content/plugins FALSE 0 wordpress_81eeec675c437946cd44da8ab3073e4b admin%7C1366650885%7Ceaad49425daa629073c9d2d3862e0afb
2013/04/21 01:23:30 DEBUG - jmeter.protocol.http.control.HC3CookieHandler: Received Cookie: wordpress_81eeec675c437946cd44da8ab3073e4b=admin%7C1366650885%7Ceaad49425daa629073c9d2d3862e0afb; path=/wp-admin; httponly From: http://192.168.52.11/wp-login.php
2013/04/21 01:23:30 DEBUG - jmeter.protocol.http.control.CookieManager: Add cookie to store 192.168.52.11 TRUE /wp-admin FALSE 0 wordpress_81eeec675c437946cd44da8ab3073e4b admin%7C1366650885%7Ceaad49425daa629073c9d2d3862e0afb

可以看到CookieManager的Check被設定為false了,關於/wp-admin路徑的Cookie已經JMeter被正常接收和設定。


6. 經驗總結
1. 遇到問題,記得看仔細看log
2. 若有debug選項,一定要代開DEBUG然後分析各種log
3. 用Google找英文資料,看看別人是否遇到過類似的(中文資料太少了)
4. 多看官方的manual文件,想到對應的部分仔細閱讀
5. 如果是開源軟體,根據一些log資訊,檢視相應的程式碼邏輯
6. 對於HTTP等協議,找到相關的RFC來讀讀吧,這才是權威的,一些程式碼、工具等都是協議的實現而已


7. 參考文件
RFC2109: http://www.ietf.org/rfc/rfc2109.txt
RFC2965: http://www.ietf.org/rfc/rfc2965.txt
JMeter manual: http://jmeter.apache.org/usermanual/component_reference.html#HTTP_Cookie_Manager
一個討論:http://jmeter.512774.n5.nabble.com/Where-do-I-find-all-of-these-cool-settings-for-the-properties-files-td5713678.html
JMeter登入WordPress問題:http://yhz61010.iteye.com/blog/1721697
介紹Cookie Manager(中文):http://desert3.iteye.com/blog/1397643

http://hc.apache.org/httpclient-3.x/xref/org/apache/commons/httpclient/cookie/CookieSpecBase.html