Fiddler实现Nginx combo(HTTP Concatenation)

作者 liang 日期 2016-05-30
Fiddler实现Nginx combo(HTTP Concatenation)

Nginx 支持这么一种资源加载方式,

1
2
http://xx.xx.com??a.js,b.js,c.js
http://xx.xx.com??e.css,f.css,g.css

同一个路径下,双问号后是同一类型逗号分隔的文件名称,效果是服务端合并这几个文件成一个文件进行传输;linux 下的nginx模块nginx_concat_module很容易实现,但是windows下的nginx却没有这个功能,所以开发调试这种资源链接异常不便。但是偶然发现fiddler编写一些脚本就可以这种资源合并。

Talk is cheap ,show me your code

头部添加如下三个 import

1
2
3
import System.Text;
import System.Text.Encoding;
import System.IO.File;

class Handlers 后面下添加如下配置

1
2
3
4
5
6
7
8
9
10
11
class Handlers{
//域名和本地文件夹的映射关联,即 r.xxx.cn 域名下的资源都映射到本地的 c:/resources 路径下
static var COMBO_CONFIG = {
'r.xxx.cn' : {
dir : "c:/resources"
}
};

...
...
...其他省略代码

class Handlers 添加以下几个方法:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
static function OnCombo(oSession : Session) {
if (!(oSession.host in COMBO_CONFIG)) {
FiddlerObject.log(oSession.host + " is not config");
return;
}

var target = "??";

var url = oSession.url;

var s = url.IndexOf(target);

if (s < 0) {
//非combo url
target = "";
s = url.IndexOf(oSession.host) + oSession.host.Length;
}

var BASE_DIR = COMBO_CONFIG[oSession.host].dir;

var e = url.IndexOf("?", s + target.Length);

var filesText = "";
if (e < 0) {
filesText = url.Substring(s + target.Length);
} else {
filesText = url.Substring(s + target.Length, e - (s + target.Length));
}
var afterHostIndex = url.IndexOf(oSession.host) + oSession.host.Length;

var files = filesText.Split(",");

var path = url.Substring(afterHostIndex, s - afterHostIndex);

var parentDir = BASE_DIR + path;

var encoding = Encoding.Default;

var content = GetFileContent(parentDir, files);

oSession.utilCreateResponseAndBypassServer();
if (encoding == System.Text.Encoding.UTF8) {
oSession.utilSetResponseBody(content);
} else {
oSession.ResponseBody = encoding.GetBytes(content.ToString());
}

var contentType = "application/javascript; charset=" + encoding.BodyName;
if (filesText.Contains(".css")) {
contentType = "text/css; charset=" + encoding.BodyName; ;
}

oSession.oResponse.headers.Add("Content-Type", contentType);
oSession.oResponse.headers.Add("Date", System.DateTime.Now.ToString());
oSession.oResponse.headers.Add("Server", "fiddler");
oSession.oResponse.headers.Add("x-fiddler-combo", "HIT");
}

static function GetFileContent(parentDir : String, files : String[]) {
var content = new System.Text.StringBuilder();

var encoding = Encoding.Default;

for (var i = 0; i < files.Length; i++) {
var fileName = files[i];
if (!System.IO.File.Exists(parentDir + fileName)) {
FiddlerObject.log(fileName + " is not exist!");
continue;
}
encoding = GetEncoding(parentDir + fileName, Encoding.Default);

content.Append(System.IO.File.ReadAllText(parentDir + fileName, encoding)); //
}
return content;
}

static function GetEncoding(file : String, defEnc : System.Text.Encoding) {
var stream = System.IO.File.OpenRead(file);{
//判断流可读?
if (!stream.CanRead)
return null;
//字节数组存储BOM
var bom = new byte[4];
//实际读入的长度
var readc = 0;

readc = stream.Read(bom, 0, 4);

stream.Close();
if (readc >= 2) {
if (readc >= 4) {
//UTF32,Big-Endian
if (CheckBytes(bom, 4, [0x00, 0x00, 0xFE, 0xFF]))
return new UTF32Encoding(true, true);
//UTF32,Little-Endian
if (CheckBytes(bom, 4, [0xFF, 0xFE, 0x00, 0x00]))
return new UTF32Encoding(false, true);
}
//UTF8
if (readc >= 3 && CheckBytes(bom, 3, [0xEF, 0xBB, 0xBF]))
return new UTF8Encoding(true);

//UTF16,Big-Endian
if (CheckBytes(bom, 2, [0xFE, 0xFF]))
return new UnicodeEncoding(true, true);
//UTF16,Little-Endian
if (CheckBytes(bom, 2, [0xFF, 0xFE]))
return new UnicodeEncoding(false, true);
}

return defEnc;
}
}

//辅助函数,判断字节中的值
static function CheckBytes(bytes : byte[], count : int, values : int[]) {
for (var i = 0; i < count; i++)
if (bytes[i] != values[i])
return false;
return true;
}

使用方法

  • 可以直接从 fiddlerScript 标签中贴入代码

  • 或者先安装fiddler的编辑插件,之后贴入代码()
  • COMBO_CONFIG 中 r.xxx.cn 换为你需要替换的域名,dir 值改为你本地资源所在的文件夹,注意路径分隔符要使用正斜线,比如这种:c:/resource

  • 在 fiddler 的 AutoResponder 建立 如下规则:(假设要替换的 r.xxx.cn/js/??a.js,b.js,c.js

  • 假如成功的话,在 response header 中会看到这个特殊的头信息

  • 完整的脚本放在 github

    注意事项

  • 现在只支持常用的 js css 资源的合并
  • 因为是通过bom识别文件格式,所以当文件为 UTF-8 编码时,注意选择 带有 bom ,否则会乱码

Fiddler 脚本功能

  Fiddler 是 windows 下web开发神器,功能强大,同时又支持脚本编写,
扩展性极其好。最后因为精力有限,没有实现用 C# 实现 图形界面。