HTTP Archive (HAR) format 是 http 协议交互的归档格式。
这个格式在本质上就是 utf8 格式的 json 字符串,存储着所有的 http 请求和 http 响应的数据,包括所有的 head 和 body。
一般,很多 proxy 类的工具,如 fiddler,charles,原来一直以为 charles 不支持保存为 har 格式,后来才知道是在 export 菜单里面:
在 charles 中,支持代理,反向代理,端口转发 这三种主要的方法获取交互的报文。
通过代理和反向代理的方式,可以获取到 http 报文,导出为 har 格式后,进行解析,可以直接生成测试脚本,以生成 loadrunner 脚本为例。
基于 python 的脚本如下:
#!/usr/bin/env python
# -*- coding: utf-8 -*-
from optparse import OptionParser
import os
import sys
import json
# check env
if sys.version_info < (3, 4):
raise RuntimeError('At least Python 3.4 is required.')
restype = ('js', 'css', 'jpg', 'gif', 'ico', 'png')
def list2dic(headers):
header_dic = dict()
for head in headers:
if head['name'] in header_dic:
header_dic[head['name']] = header_dic[head['name']] + ',' + head['value']
else:
header_dic[head['name']] = head['value']
return header_dic
def dictoand(dct):
res_list = list()
for tp in dct:
res_list.append('%s=%s' % (tp['name'], tp['value']))
return '&'.join(res_list)
def dict2lr(lrsc):
tmpl = '''
web_custom_request("%(name)s",
"URL=%(url)s",
"Method=%(method)s",
"Resource=%(res)s",
"Referer=%(referer)s",
"EncType=%(enctype)s",
"Body=%(body)s",
LAST);'''
# url
url = lrsc['url']
method = lrsc['method']
name = url.split('/')[-1]
name = name.split('?')[0]
suff = url.split('.')[-1]
# Resource type
global restype
res = '0'
if suff in restype:
res = '1'
# Content-Type
enctype = ''
if 'Content-Type' in lrsc:
enctype = lrsc['Content-Type']
# Referer
referer = ''
if 'Referer' in lrsc:
referer = lrsc['Referer']
# Body
body = ''
if 'posttext' in lrsc:
body = lrsc['posttext']
elif 'postparams' in lrsc:
body = dictoand(lrsc['postparams'])
body = body.replace('"', '\\"')
res = tmpl % {'name': name, 'url': url, 'method': method, 'enctype': enctype, 'referer': referer, 'res': res,
'body': body}
# Head
if 'SOAPAction' in lrsc:
res = ("\n" + ' web_add_header("SOAPAction", "%s")' + ";\n" + res) % lrsc['SOAPAction']
return res
def parhar(harfile):
res = list()
try:
FH = open(harfile, mode='r', encoding='utf-8', closefd=True)
all = json.load(FH)
FH.close()
except Exception as ex:
print('Open har file errr: %s' % ex)
quit()
har_ver = all['log']['version']
creater = all['log']['creator']['name']
entries = all['log']['entries']
ct = len(entries)
for et in entries:
stm = et['startedDateTime']
req = et['request']
rsp = et['response']
lrsc = dict()
if '_charlesStatus' in rsp and rsp['_charlesStatus'] != 'Complete':
continue
lrsc['method'] = req['method']
lrsc['url'] = req['url']
headers = req['headers']
# http head
header_dic = list2dic(headers)
if 'SOAPAction' in header_dic:
lrsc['SOAPAction'] = header_dic['SOAPAction'].replace('"', '\\"')
if 'Referer' in header_dic:
lrsc['Referer'] = header_dic['Referer']
if 'Content-Type' in header_dic:
lrsc['Content-Type'] = header_dic['Content-Type']
if lrsc['method'] == 'GET':
pass
elif lrsc['method'] == 'POST':
if 'postData' in req:
if 'text' in req['postData']:
lrsc['posttext'] = req['postData']['text']
if 'params' in req['postData']:
lrsc['postparams'] = req['postData']['params']
if 'mimeType' in req['postData']:
lrsc['postmime'] = req['postData']['mimeType']
else:
continue
res.append(dict2lr(lrsc))
return res
if __name__ == '__main__':
parse = OptionParser()
parse.add_option("-f", action="store", dest="harfile", help='harfile path')
parse.add_option("-o", action="store", dest="lrfile", help='action.c path')
(options, args) = parse.parse_args()
if options.harfile is None or options.lrfile is None:
parse.print_help()
quit()
if not os.path.exists(options.harfile):
print('Har file %s not exist' % options.harfile)
quit()
res = parhar(options.harfile)
file = open(options.lrfile, mode='w', encoding='utf-8')
for sc in res:
file.write(sc)
file.write("\n")
file.close()
print('Output to %s' % options.lrfile)
生成的脚本,可以直接 copy 到 LR 中使用,根据需要做参数化和关联,对于不支持通过 lr 录制的,如接口类,app 类的,通过这种方式可以更快的生成脚本。
可能还存在部分未考虑到的问题,如请求并发,编码等问题。需要根据实际情况调整。
对于其他工具,也可以自己解析 har 生成对应的测试案例、脚本等。
另外,在 charles 中,还支持 web interface,开启后,可以远程管理 charles,打开或关闭选项,下载 har 文件,有需要的可以自己写请求实时远程管理,做到进一步的自动化。
有需要就自己折腾吧。