1 |
|
2 |
|
3 |
|
4 |
|
5 |
|
6 | var 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 |
|
27 |
|
28 |
|
29 |
|
30 |
|
31 |
|
32 |
|
33 |
|
34 |
|
35 |
|
36 |
|
37 |
|
38 | exports = 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 |
|
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 |
|
100 | var pageContent = J(fs.readFileSync(tplPath).toString(), {folder : url, back: join(url, '..').replace(/\\/g, '/'), folderInfo:files});
|
101 |
|
102 |
|
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 |
|
112 | if(realPageRelativePath.match(/.*\.html$/)){
|
113 |
|
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 |
|
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 |
|
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 |
|
194 | function 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 |
|
214 |
|
215 |
|
216 |
|
217 |
|
218 |
|
219 |
|
220 |
|
221 |
|
222 | exports.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 |
|
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 |
|
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 | }
|