UNPKG

13.2 kBJavaScriptView Raw
1/**
2 * 处理预览网页请求。
3 * 如果是目录,则返回目录列表
4 */
5
6var fs = require('fs')
7 , path = require('path')
8 , join = path.join
9 , _ = require('underscore')
10 , mime = require('mime')
11 , util = require('util')
12 , http = require('http')
13 , debug = require('debug')('clam:jspage')
14 , config = require('./config.js')
15 , compile = require('./compile.js')
16 , proxy = require("./proxy.js")
17 , J = require("juicer")
18 , isUtf8 = require('is-utf8')
19 , crypto = require('crypto')
20 , iconv = require('iconv-lite')
21 , urlLib = require('url')
22 , conf = {
23 "templateDir" : "../ui/",
24 "template": "folder-viewer.html"
25 };
26//var pathUtil = require('path');
27
28/**
29 * 传入参数option格式
30 * {
31 * root: '/',
32 * mapping: {'/aaaa': ['aaa/bbb.html', 'ccc/aaa.html']},
33 * maxAge : 1000,
34 * charset : 'gbk',
35 * modsDir: [], //相对项目目录的模块目录位置,可以有多个
36 * }
37 */
38exports = module.exports = function jspage(basePath) {
39 basePath = basePath ? basePath : "src";
40 var prjInfo = config.get('project');
41 var root = config.root();
42 var pageInfo = config.get('page');
43 var mapping = {};
44 _.each(pageInfo, function(value){
45 mapping[value.url] = value.name;
46 });
47 debug('页面映射%s, \n %s', util.inspect(pageInfo), util.inspect(mapping));
48 var modsDir = path.join(root, basePath);
49
50 config.on('pageChange', function(){
51 pageInfo = config.get('page');
52 mapping = {};
53 _.each(pageInfo, function(value){
54 mapping[value.url] = value.name;
55 });
56 debug('page.json文件被修改:%s', util.inspect(mapping));
57 });
58 return function(req, res, next) {
59 var URLParse = urlLib.parse(req.url);
60
61 var url = URLParse.pathname;
62 var pageRootPath = join(root, basePath); // 页面根目录
63
64 //请求的url真正相对页面根目录的路径。因为可以有目录映射。
65 var realPageRelativePath = exports.mappedFile(url, pageRootPath, mapping);
66 debug('页面请求%s -> %s', url.replace(/^[\/]/, ''), util.inspect(realPageRelativePath));
67 //既不是文件。也不是目录
68 if(!realPageRelativePath || realPageRelativePath === '') {
69 if (!prjInfo.hostsMap || !prjInfo.hostsMap[req.headers.host]) {
70 res.writeHead(404, { 'Content-Type': 'text/html;charset=utf-8'});
71 res.end('404 Error, File not found.');
72 return;
73 }
74
75 proxy.fetch(
76 req.url,
77 prjInfo.hostsMap ? prjInfo.hostsMap[req.headers.host] : '',
78 function(content, code) {
79 if (code == 200) {
80 res.end(isUtf8(content) ? content.toString() : iconv.decode(content, 'gbk'));
81 }
82 else {
83 res.writeHead(code, { 'Content-Type': 'text/html;charset=utf-8'});
84 res.end(content);
85 }
86 }
87 );
88 return;
89 }
90 //返回数组,表示是目录,需要显示目录。
91 if(util.isArray(realPageRelativePath)){
92 var files = realPageRelativePath.sort();
93 debug('files', util.inspect(files));
94 var tplPath = path.join(__dirname, conf.templateDir, conf.template);
95 _.map(files, function(file){
96 file.path = path.join(url, file.name);
97 return file;
98 })
99 // this is using juicer engine only
100 var pageContent = J(fs.readFileSync(tplPath).toString(), {folder : url, back: join(url, '..').replace(/\\/g, '/'), folderInfo:files});
101 // this is using an include render and juicer
102 // var pageContent = J(compile.render(tplPath, [modsDir], prjInfo.cdnPath), {folderInfo: files});
103 pageContent = new Buffer(pageContent);
104 res.setHeader('Content-Type', 'text/html;charset=utf-8');
105 res.setHeader('Content-Length', pageContent.length);
106 res.end(pageContent);
107 return;
108 }
109 //页面在服务器真实绝对路径
110 var realAbsPapePath = join(pageRootPath, realPageRelativePath);
111 //只处理html文件的SSI等高级语法
112 if(realPageRelativePath.match(/.*\.html$/)){
113 //html文件有片段和页面之分
114 var rootContent = fs.readFileSync(realAbsPapePath);
115 rootContent = isUtf8(rootContent) ? rootContent.toString() : iconv.decode(rootContent, 'gbk');
116
117 var pageContent = '', tmsContent = [];
118 try {
119 pageContent = compile.render(realAbsPapePath, [modsDir], prjInfo.cdnPath, rootContent, url);
120 // 解析assetsTool
121 pageContent = compile.parseAssetsTool(pageContent);
122 }
123 catch(e) {
124 console.log(util.inspect(e));
125 res.writeHead(500, {'Content-Type' : 'text/html;charset=utf-8'});
126 res.end('500 Error, Internal Server Error.');
127 return;
128 }
129
130 var matches = pageContent.match(/(<!--#tms file=")(http:\/\/([^\:|\/]+)(\:\d*)?(.*\/)([^#|\?|\n]+)?(#.*)?(\?.*)?)("-->)/ig),
131 m = [];
132 matches = _.uniq(matches ? matches : []);
133 for (var i=0; i<matches.length; i++) {
134 m = matches[i].match(/(<!--#tms file=")(http:\/\/([^\:|\/]+)(\:\d*)?(.*\/)([^#|\?|\n]+)?(#.*)?(\?.*)?)("-->)/i);
135 if (m && m[2] && m[3]) {
136 tmsContent.push(false);
137 proxy.fetch(
138 m[2],
139 m[3],
140 (function(i){
141 return function(content) {
142 tmsContent[i] = isUtf8(content) ? content.toString() : iconv.decode(content, 'gbk');
143 sendData();
144 }
145 })(i)
146 );
147 }
148 }
149
150 var sendData = function() {
151 for (var j = 0, len = matches.length; j < len; j++) {
152 if (tmsContent[j] === false) {
153 return;
154 }
155 else {
156 pageContent = pageContent.replace(new RegExp(matches[j], 'g'), tmsContent[j]);
157 }
158 }
159 if (!pageContent.match(/<\!DOCTYPE/)) {
160 pageContent = '<!DOCTYPE html>'+pageContent;
161 }
162
163 if(prjInfo.charset[0].match(/gbk/i)){
164 pageContent = iconv.encode(pageContent, 'gbk');
165 }
166 //输出前必须转换为Buffer,以便为Content-Length取得正确的length
167 if (!(pageContent instanceof Buffer)) {
168 pageContent = new Buffer(pageContent);
169 }
170 res.setHeader('Content-Type', mime.lookup(realPageRelativePath) + '; charset=' + prjInfo.charset[0]);
171 res.setHeader('Content-Length', pageContent.length);
172 res.setHeader('Transfer-Encoding', 'chunked');
173 res.end(pageContent);
174 }
175 sendData();
176 }
177 else {
178 //其他格式的文件按照原内容返回
179 var stream = fs.createReadStream(realAbsPapePath, {});
180 req.on('close', stream.destroy.bind(stream));
181 stream.pipe(res);
182
183 stream.on('error', function(err){
184 if (res.headerSent) {
185 req.destroy();
186 } else {
187 next(err);
188 }
189 });
190 }
191 };
192};
193
194function parseToDir(maps){
195 var virtualRoot = {};
196 for(var k in maps){
197 var currentObj = virtualRoot;
198 var dirs = k.split('/');
199 dirs[0] = '/';
200 for(var i = 0, len = dirs.length; i < len; i++){
201 var currentDir = currentObj[dirs[i]];
202 if(!currentDir){
203 currentObj[dirs[i]] = {};
204 }
205 currentObj = currentObj[dirs[i]];
206 }
207 }
208 return virtualRoot;
209}
210
211
212/**
213 * 根据url和映射关系返回对应真正的内容
214 * 返回值有3种情况
215 * 如果为null,则表示此url既不能对应到虚拟路径,也不能对应到实际路径。
216 * 如果为字符串,则表示此url最终对应到一个文件。
217 * 如果为数组,则表示此url最终对应到一个目录。
218 * @param url
219 * @param maps
220 * @return {*}
221 */
222exports.mappedFile = function mappedFile(url, root, maps){
223 var subDirs = [];
224 debug('url:%s, map定义:%s',url, util.inspect(maps));
225
226 //在表中定义过的映射,根据不同情况返回子目录或已存在内容的文件地址
227 var mappedFile = maps[url];
228 if(mappedFile){
229 var mappedRealDir = join(root, mappedFile);
230 var state = null;
231 if(fs.existsSync(mappedRealDir)){
232 state = fs.statSync(mappedRealDir);
233 if(state.isDirectory()){
234 subDirs = fs.readdirSync(mappedRealDir);
235 subDirs = _.map(subDirs, function(subFile){
236 var state = fs.statSync(path.join(mappedRealDir, subFile));
237 return {name: subFile, isDir: state.isDirectory()};
238 });
239 return subDirs;
240 }
241 else{
242 return mappedFile;
243 }
244 }
245 else{
246 return null;
247 }
248 }
249
250 //寻找真实root目录下的相关文件,如果是目录,需要把目录的内容和映射定义中的合并起来
251 var realDir = join(root, url);
252 debug('寻找物理目录:%s', realDir);
253 if (realDir.match(/.htm$/) && !fs.existsSync(realDir)) {
254 realDir += 'l';
255 }
256 if(fs.existsSync(realDir)){
257 var state = fs.statSync(realDir);
258 if(!state.isDirectory()){
259 return path.relative(root, realDir);
260 }
261
262 //寻找子目录
263 var realsubDirs = fs.readdirSync(realDir);
264 realsubDirs = _.map(realsubDirs, function(subFile){
265 var state = fs.statSync(path.join(realDir, subFile));
266 return {name: subFile, isDir: state.isDirectory()};
267 });
268 subDirs = subDirs.concat(realsubDirs);
269 }
270
271 //不是表定义中的映射,url有可能是定义映射key的一部分
272 var virtual = parseToDir(maps);
273
274 var currentVirtual = null;
275 debug('url is : %s',url);
276 var dirs = url.split('/');
277 dirs[0] = '/';
278 debug(util.inspect(dirs));
279 for (var i = 0; i < dirs.length; i++) {
280 if(dirs[i] === '') continue;
281 if(virtual[dirs[i]]){
282 virtual = virtual[dirs[i]];
283 currentVirtual = virtual;
284 }
285 else{
286 currentVirtual = null;
287 break;
288 }
289 };
290 debug('虚拟目录%s', util.inspect(currentVirtual));
291 if(currentVirtual){
292 for(var subPath in currentVirtual){
293 var isDir = false;
294 for(var subSubPath in currentVirtual[subPath]){
295 isDir = true;
296 break;
297 }
298 var subPathMapping = maps[path.join(url, subPath)];
299 if(subPathMapping){
300 subPathMapping = path.join(root, subPathMapping);
301 if(fs.existsSync(subPathMapping)){
302 state = fs.statSync(subPathMapping);
303 if(state.isDirectory()){
304 isDir = true;
305 }
306 }
307 }
308 var exist = _.find(subDirs, function(ss){
309 return ss.name === subPath;
310 });
311 if(!exist){
312 subDirs.push({name: subPath, isDir: isDir});
313 }
314 }
315
316 }
317 if (subDirs.length !== 0){
318 return subDirs;
319 }
320
321 var longestMatchedKey = '';
322 for(var j = 0; j < i; j++){
323 longestMatchedKey = path.join(longestMatchedKey, dirs[j]);
324 }
325 debug('最长匹配路径转换后key%s', longestMatchedKey);
326 if(maps[longestMatchedKey]){
327 var lastPath = url.replace(longestMatchedKey, '');
328 var longestMatchedPath = path.join(root, maps[longestMatchedKey], lastPath);
329 debug('最长匹配路径转换后%s', longestMatchedPath);
330 if(fs.existsSync(longestMatchedPath)){
331 var longgestMatchedState = fs.statSync(longestMatchedPath);
332 if(longgestMatchedState.isDirectory()){
333 var longgestMatchedSubDirs = fs.readdirSync(longestMatchedPath);
334 longgestMatchedSubDirs = _.map(longgestMatchedSubDirs, function(subFile){
335 var state = fs.statSync(path.join(longestMatchedPath, subFile));
336 return {name: subFile, isDir: state.isDirectory()};
337 });
338 return longgestMatchedSubDirs;
339 }
340 else{
341 return path.join(maps[longestMatchedKey], lastPath);
342 }
343 }
344 else{
345 return null;
346 }
347 }
348}