實際業務中,會要求 HTTP 協議中附加 MD5 校驗字段, 防止請求參數被惡意篡改, 對於開發同學來說, 這是個很簡單的需求。 但是給自動化測試增加了難度, Jmeter 原生不支持這個功能,應測試同學要求,開發了一個簡單的小插件。
一、功能分析
1. MD5 值生成規則:
Step1, HTTP 請求增加新參數 ct, 值為當前時間毫秒數;
Step2, 獲取HTTP 請求所有參數,放入 list;
Step3, List 增加新參數 key, 值根據通信協議的雙方約定(不在 http請求中傳遞 );
Step4, 把 List 按參數名進行排序,把排序后的參數值進行拼接, 對拼接結果執行 MD5 運算;
Step5, Http請求增加新參數 code, 值是 Step4 計算得出的值。
注:不同業務此規則可能略有差異,請根據實際情況實現。
2. MD5 值需要在 HTTP 請求之前添加 (屬於 Jmeter 前置處理器范圍)。
二、需要解決的問題
1. 在 Jmeter 的 HTTP 請求中獲取參數、 增加參數
2. 實現 MD5 的具體邏輯
3. 在合適的時機把生成的 MD5 校驗值附加到 HTTP 請求參數中
4. 集成到 Jmeter 菜單中,方便使用。
三、解決步驟
1. 因之前沒有 Jmeter 插件開發經驗, 需要先學習一下如何開發。 網上搜集了一下資料沒有太明確的方案;不過Jmeter本身有很多插件,就從這里入手,分析一下現有插件的實現方式,然后用相同的方式來完成插件。
2. 到 Apache 官網 下載 Jmeter 的源代碼(我使用的2.11版), 根據 Jmeter 界面中看到的關鍵字來定位插件代碼位置。
3. 開發的是 Http Sampler 插件,那么,我們先搜索一下Http Sampler關鍵字, 可以找到 HttpSampler.java;閱讀代碼可以得到問題1的答案。
4. 問題2不在贅述。
5. 分析問題3, 我們需要在 HTTP 請求發送前, 進行插件調用, 可以得知這個插件應該是屬於前置處理器范圍的。 通過分析前置處理器的代碼應該可以解決這個問題。Jmeter 插件中第一個前置處理器是 BeanShell PreProcessor, 用 BeanShell 和 PreProcessor 關鍵字查找源代碼, 查到了類 BeanShellPreProcessor.java;閱讀這個類及父類代碼可以得到問題3、問題4的答案。
四、具體代碼
1. MD5PreProcessor.java

1 public class MD5PreProcessor extends AbstractTestElement implements TestStateListener, PreProcessor, Serializable { 2 3 private static final long serialVersionUID = -1L; 4 private static final Logger LOGGER = LoggingManager.getLoggerForClass(); 5 6 /** 7 * Default constructor. 8 */ 9 public MD5PreProcessor() { 10 } 11 12 @Override 13 public void testStarted() { 14 } 15 16 @Override 17 public void testStarted(String host) { 18 } 19 20 @Override 21 public void testEnded() { 22 } 23 24 @Override 25 public void testEnded(String host) { 26 } 27 28 /* 29 * ------------------------------------------------------------------------ 30 * Methods implemented from interface org.apache.jmeter.config.Modifier 31 * ------------------------------------------------------------------------ 32 */ 33 34 @Override 35 public void process() { 36 Sampler sam = getThreadContext().getCurrentSampler(); 37 HTTPSamplerBase sampler = null; 38 if (!(sam instanceof HTTPSamplerBase)) { 39 return; 40 } else { 41 sampler = (HTTPSamplerBase) sam; 42 } 43 44 // 排序參數和header(appversion, os) 45 java.util.List<KeyValue> list = new ArrayList<KeyValue>(); 46 47 48 sampler.addArgument("ct", System.currentTimeMillis() + ""); 49 50 // 添加參數列表 51 Arguments arguments = sampler.getArguments(); 52 PropertyIterator iter = arguments.iterator(); 53 while(iter.hasNext()) { 54 Argument arg = getFromJMeterProperty(iter.next()); 55 KeyValue kv = new KeyValue(arg.getName(), arg.getValue()); 56 list.add(kv); 57 } 58 59 // 添加header: os appVersion 60 HeaderManager headerManager = sampler.getHeaderManager(); 61 for(int i = 0; i < headerManager.getHeaders().size(); i++) { 62 Header a = headerManager.getHeader(i); 63 if("os".equals(a.getName()) || "appVersion".equals(a.getName())) { 64 list.add(new KeyValue(a.getName(), a.getValue())); 65 } 66 } 67 68 69 // 加鹽 70 list.add(new KeyValue("key", "+MrK}6seb#$E")); 71 72 StringBuffer sbu = new StringBuffer(); 73 74 // 按順序拼接所有參數值 75 Collections.sort(list); 76 for(KeyValue kv : list) { 77 //LOGGER.info("key: "+kv.getKey()+", value:"+kv.getValue()); 78 sbu.append(kv.getValue()); 79 } 80 81 try { 82 MessageDigest md = MessageDigest.getInstance("MD5"); 83 byte[] md5sum = md.digest(URLEncoder.encode(sbu.toString(), "UTF-8").getBytes()); 84 String md5Code = new BigInteger(1, md5sum).toString(16); 85 86 while(md5Code.length() < 32 ){ 87 md5Code = "0"+md5Code; 88 } 89 90 LOGGER.info("before: "+sbu.toString()+",encode:"+URLEncoder.encode(sbu.toString(), "UTF-8")+",end:"+md5Code); 91 sampler.addArgument("code", md5Code); 92 } catch(Exception e) { 93 e.printStackTrace(); 94 } 95 96 97 98 } 99 100 private Argument getFromJMeterProperty(JMeterProperty o) { 101 return (Argument)o.getObjectValue(); 102 } 103 104 /* 105 * ------------------------------------------------------------------------ 106 * Methods 107 * ------------------------------------------------------------------------ 108 */ 109 private class KeyValue implements Comparable<KeyValue> { 110 private String key; 111 private String value; 112 113 public KeyValue(String key, String value) { 114 this.key = key; 115 this.value = value; 116 } 117 118 @Override 119 public int compareTo(KeyValue o) { 120 int result = this.key.compareTo(o.key); 121 if(result == 0) { 122 result = this.value.compareTo(o.value); 123 } 124 125 return result; 126 } 127 128 public String getKey() { 129 return key; 130 } 131 132 public String getValue() { 133 return value; 134 } 135 } 136 137 }
2. Md5PreProcessorGui.java

public class Md5PreProcessorGui extends AbstractPreProcessorGui { private static final long serialVersionUID = 100L; private static final Logger LOGGER = LoggingManager.getLoggerForClass(); public Md5PreProcessorGui() { createGui(); } public String getStaticLabel() { return "MD5 Encode"; } public String getLabelResource() { return getClass().getCanonicalName(); } public void configure(TestElement element) { super.configure(element); } public TestElement createTestElement() { MD5PreProcessor sampler = new MD5PreProcessor(); modifyTestElement(sampler); return sampler; } public void modifyTestElement(TestElement element) { element.clear(); configureTestElement(element); } public void clearGui() { super.clearGui(); } private void createGui() { setLayout(new BorderLayout(0, 5)); setBorder(makeBorder()); this.setLayout(new BorderLayout(0, 5)); this.setBorder(this.makeBorder()); this.add(this.makeTitlePanel(), "North"); VerticalPanel mainPanel = new VerticalPanel(); add(mainPanel, "Center"); } }