core.js
9.63 KB
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
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
/*jshint -W100*/
'use strict';
var assign = require('object-assign');
var typeOf = require('type-of');
var each = require('foreach');
var cheerioExtend = require('./cheerio-extend');
var util = require('./cheerio/util.js');
var encoding = require('./encoding');
var client = require('./client');
var pkg = require('../package.json');
var browsers = require('./browsers.json');
/**
* cheerio-httpcli内部からプロパティを直接更新する際の黒魔術
*/
var propertyUpdater = {
// 内部的にプロパティを直接更新する際の照合キー
Key: Math.random().toString(36).substr(2),
// プロパティ更新時の値を黒魔術で包み込む
wrap: function (value) {
return [ this.key, value ];
},
// プロパティ更新時の値を黒魔術から取り出す
unwrap: function (value) {
if ((value instanceof Array) &&
value.length === 2 && value[0] === this.key) {
return value[1];
}
util.colorMessage('DEPRECATED', 'direct property update will be refused in the future. use set(key, value)');
//throw new Error(direct property update is not permitted. use set(key, value)');
return value;
}
};
// リクエストヘッダを作り直す
var rebuildHeaders = function (value) {
var tmp = {};
var nullKeys = [];
// リクエストヘッダは小文字に統一する & 値にnullが入っているキーは削除
each(value, function (val, key) {
if (value[key] === null) {
nullKeys.push(key.toLowerCase());
}
tmp[key.toLowerCase()] = val;
});
each(nullKeys, function (key) {
delete tmp[key];
});
return tmp;
};
// 通常プロパティ作成(property変数内で管理)
var defineNormalProperties = function (cli) {
/*eslint-disable key-spacing*/
var property = {
// リクエストヘッダ
headers : { types: [ 'object' ], value: null },
// タイムアウトまでの時間
timeout : { types: [ 'number' ], value: null },
// gzip転送する/しない
gzip : { types: [ 'boolean' ], value: null },
// Refererを自動設定する/しない
referer : { types: [ 'boolean' ], value: null },
// <meta[http-equiv=refresh]>を検知してリダイレクトする/しない
followMetaRefresh : { types: [ 'boolean' ], value: null },
// 受信を許可する最大のサイズ
maxDataSize : { types: [ 'number', 'null' ], value: null },
// XML自動判別を使用しない
forceHtml : { types: [ 'boolean' ], value: null },
// requestモジュールに渡すagentOptions
agentOptions : { types: [ 'object' ], value: null },
// デバッグオプション
debug : { types: [ 'boolean' ], value: null }
};
/*eslint-enable key-spacing*/
// プロパティ登録(直接更新時にはDEPRECATEDメッセージを表示)
Object.keys(property).forEach(function (prop) {
Object.defineProperty(cli, prop, {
enumerable: true,
get: function () {
// TODO 現在は直接更新も可としているのでコメントアウトしておく
//if (typeOf(property[prop].value) === 'object') {
// // オブジェクトの場合は複製を返す
// var copy = {};
// each(property[prop].value, function (val, key) {
// copy[key] = val;
// });
// return copy;
//}
return property[prop].value;
},
set: function (value) {
value = propertyUpdater.unwrap(value);
// 型チェック
var types = property[prop].types;
var vtype = typeOf(value);
if (types.indexOf(vtype) === -1 || (vtype === 'number' && value < 0)) {
util.colorMessage(
'WARNING',
'invalid value: ' + String(value) + '. '
+ 'property "' + prop + '" can accept only ' + types.join(' or ')
);
return;
}
// headersのキーを全部小文字に変換 & 値がnullのキーは削除
if (prop === 'headers') {
value = rebuildHeaders(value);
}
property[prop].value = value;
}
});
});
};
// 特殊プロパティ作成(動的に値を用意する)
var defineSpecialProperties = function (cli) {
// browserプロパティ
Object.defineProperty(cli, 'browser', {
enumerable: true,
get: function () {
// User-Agentとブラウザテンプレが一致するものを探す
var ua = this.headers['user-agent'];
if (! ua) {
return null;
}
var browser = Object.keys(browsers).filter(function (name) {
return (browsers[name] === ua);
});
if (browser.length > 0) {
return browser[0];
}
return 'custom';
},
set: function (type) {
type = propertyUpdater.unwrap(type);
if (type in browsers) {
this.set('headers', {
'User-Agent': browsers[type]
});
return;
}
util.colorMessage('WARNING', 'unknown browser: ' + type);
}
});
// iconvプロパティ
Object.defineProperty(cli, 'iconv', {
enumerable: true,
get: function () {
return encoding.getIconvType();
},
set: function (icmod) {
icmod = propertyUpdater.unwrap(icmod);
if (! encoding.iconvLoad(icmod)) {
throw new Error('Cannot find module "' + icmod + '"');
}
}
});
// バージョン情報プロパティ
Object.defineProperty(cli, 'version', {
enumerable: true,
get: function () {
return pkg.version;
}
});
};
// WIP マルチインスタンス化に向けて構造変更中
var CheerioHttpCli = (function () {
/**
* コンストラクタ
*/
function CheerioHttpCli() {
defineNormalProperties(this);
defineSpecialProperties(this);
}
/**
* プロパティや内部情報の初期化
*/
CheerioHttpCli.prototype.reset = function () {
// リクエストヘッダ
this.set('headers', {}, true);
// タイムアウトまでの時間(効いているかどうか不明)
this.set('timeout', 30000);
// gzip転送する/しない
this.set('gzip', true);
// Refererを自動設定する/しない
this.set('referer', true);
// <meta[http-equiv=refresh]>を検知してリダイレクトする/しない
this.set('followMetaRefresh', false);
// 受信を許可する最大のサイズ
this.set('maxDataSize', null);
// XML自動判別を使用しない
this.set('forceHtml', false);
// requestモジュールに渡すagentOptions
this.set('agentOptions', {}, true);
// デバッグオプション
this.set('debug', false);
client.reset();
return this;
};
/**
* プロパティを操作
*
* @param name 操作するプロパティ名
* @param value 挿入する値
* @param nomerge trueのときマージを行わない
*/
CheerioHttpCli.prototype.set = function (name, value, nomerge) {
// 特殊プロパティ
if ([ 'browser', 'iconv' ].indexOf(name) !== -1) {
this[name] = propertyUpdater.wrap(value);
return this;
}
// プロパティが存在するかチェック
if (! Object.keys(this).some((function (prop) {
return (prop === name && typeOf(this[prop]) !== 'function');
}).bind(this))) {
throw new Error('no such property "' + name + '"');
}
// オブジェクトへの代入ならマージする(黒魔術使用)
if (! nomerge && typeOf(this[name]) === 'object' && typeOf(value) === 'object') {
this[name] = propertyUpdater.wrap(assign(this[name], value));
} else {
this[name] = propertyUpdater.wrap(value);
}
return this;
};
/**
* [DEPRECATED] 使用するiconvモジュールを指定
*
* @param icmod iconvモジュール名(iconv|iconv-jp|iconv-lite)
*/
CheerioHttpCli.prototype.setIconvEngine = function (icmod) {
util.colorMessage('DEPRECATED', 'setIconvEngine() will be removed in the future. use set("iconv", value)');
this.set('iconv', icmod);
};
/**
* [DEPRECATED] ブラウザごとのUser-Agentをワンタッチ設定
*
* @param browser ブラウザ種類(see browsers.json)
* @return 設定できた/できなかった
*/
CheerioHttpCli.prototype.setBrowser = function (type) {
util.colorMessage('DEPRECATED', 'setBrowser() will be removed in the future. use set("browser", value)');
this.set('browser', type);
};
/**
* GETによる非同期httpリクエストを実行
*
* @param url リクエスト先のURL
* @param param リクエストパラメータ
* @param encode 取得先のHTMLのエンコーディング(default: 自動判定)
* @param callback リクエスト完了時のコールバック関数(err, cheerio, response, body)
*/
CheerioHttpCli.prototype.fetch = function (url, param, encode, callback) {
return client.run('GET', url, param, encode, callback);
};
/**
* GETによる同期httpリクエストを実行
*
* @param url リクエスト先のURL
* @param param リクエストパラメータ
* @param encode 取得先のHTMLのエンコーディング(default: 自動判定)
* @param callback リクエスト完了時のコールバック関数(err, cheerio, response, body)
*/
CheerioHttpCli.prototype.fetchSync = function (url, param, encode) {
return client.run('GET', url, param, encode, 'sync');
};
return CheerioHttpCli;
})();
var mainClient = new CheerioHttpCli();
// clientオブジェクト内で使用する外部オブジェクトを登録
mainClient.reset();
client.core = mainClient;
client.encoding = encoding;
client.cheerio = cheerioExtend(encoding, client);
module.exports = mainClient;