这半个多月一直在学Node.js,还是在入门阶段,不过已经对Node很感兴趣了。这里介绍一个简单的静态文件服务器,总结一下心得体会。为什么说简单呢,因为虽然基本功能都有,但是没加路由,还有没考虑一些安全性的东西。后面我会继续来总结完善。
本文参考了Node大神朴灵11年写的一篇博文(强烈建议读一读,虽然有一些接口有些老,但是搭建服务器的思路,各方面都有涉及),还有这位大神前辈的博客,以及Node.js 6.x版本的文档,stackoverflow一些答案。
如果哪里说得不对或者哪里有问题,请劳烦您指正,我会虚心接受。
正文如下:
一个Web服务器应具备以下几个功能:
1、能显示以.html/.htm结尾的Web页面
2、能直接打开以.js/.css/.json/.text结尾的文件内容
3、显示图片资源
4、自动下载以.apk/.docx/.zip结尾的文件
5、形如http://xxx.com/a/b/ , 则查找b目录下是否有index.html,如果有就显示,如果没有就列出该目录下的所有文件及文件夹,并可以进一步访问。
6、形如http://xxx.com/a/b, 则作301重定向到http://xxx.com/a/b/ , 这样可以解决内部资源引用错位的问题。
总体的思路如下:
1.先加载需要用到的几个模块 url(解析request,截取路径) path(解析路径) fs(文件读写操作) http(起服务器)
2.切出来请求的url和路径 (记得解码,防止中文乱码)
3.根据路径有无扩展名以及是否以“/”结尾作判断,重定向
4.根据路径来查找资源文件
OK,接下来上代码,我有详细的标注注释(咳,方便以后来查漏补缺)
前面有提到参考的那两篇博文的年代有些久远(其实也就4,5年前..),以致一些接口6.x版本的Node.js已经不支持了,或者一些方法近些年有了最佳实践。
说一下重构的地方:
1.判断路径类型,不使用fs.exist(),而是换成fs.stat方法
2.将回调换成了箭头函数。
3.我一直没查到getContentType这个方法.. 所以还是使用mime映射来传入content-type 其实已经专门有mime模块来处理这个问题了。
4.做了一下模块化处理
第一个模块: app.js 启动模块
"use strict";
//加载所需要的模块
var http = require('http');
var processRequest = require('./server');
//创建服务,这里很机智的把对response和request的处理封装成一个匿名函数,传入createServer中
//也可以直接在里面写,但是看起来不是很整洁
var httpServer = http.createServer((req, res) => {
processRequest(req, res);
});
var port = 8080;
//指定一个监听的接口
httpServer.listen(port, function() {
console.log(`app is running at port:${port}`);
});
第二个模块:mime.js 存放类型映射
module.exports = {
"css": "text/css",
"gif": "image/gif",
"html": "text/html",
"ico": "image/x-icon",
"jpeg": "image/jpeg",
"jpg": "image/jpeg",
"js": "text/javascript",
"json": "application/json",
"pdf": "application/pdf",
"png": "image/png",
"svg": "image/svg+xml",
"swf": "application/x-shockwave-flash",
"tiff": "image/tiff",
"txt": "text/plain",
"wav": "audio/x-wav",
"wma": "audio/x-ms-wma",
"wmv": "video/x-ms-wmv",
"xml": "text/xml"
};
第三个模块,也是我们的核心模块 server.js
var url = require('url');
var fs = require('fs');
var path = require('path');
var mime = require('./mime');
function processRequest(request, response) {
//request里面切出标识符字符串
var requestUrl = request.url;
//url模块的parse方法 接受一个字符串,返回一个url对象,切出来路径
var pathName = url.parse(requestUrl).pathname;
//对路径解码,防止中文乱码
var pathName = decodeURI(pathName);
//解决301重定向问题,如果pathname没以/结尾,并且没有扩展名
if (!pathName.endsWith('/') && path.extname(pathName) === '') {
pathName += '/';
var redirect = "http://" + request.headers.host + pathName;
response.writeHead(301, {
location: redirect
});
//response.end方法用来回应完成后关闭本次对话,也可以写入HTTP回应的具体内容。
response.end();
};
//获取资源文件的绝对路径
var filePath = path.resolve(__dirname + pathName);
console.log(filePath);
//获取对应文件的文档类型
//我们通过path.extname来获取文件的后缀名。由于extname返回值包含”.”,所以通过slice方法来剔除掉”.”,
//对于没有后缀名的文件,我们一律认为是unknown。
var ext = path.extname(pathName);
ext = ext ? ext.slice(1) : 'unknown';
//未知的类型一律用"text/plain"类型
var contentType = mime[ext] || "text/plain";
fs.stat(filePath, (err, stats) => {
if (err) {
response.writeHead(404, { "content-type": "text/html" });
response.end("<h1>404 Not Found</h1>");
};
//没出错 并且文件存在
if (!err && stats.isFile()) {
response.writeHead(200, { "content-type": contentType });
//建立流对象,读文件
var stream = fs.createReadStream(filePath);
//错误处理
stream.on('error', function() {
response.writeHead(500, { "content-type": contentType });
response.end("<h1>500 Server Error</h1>");
});
//读取文件
stream.pipe(response);
//response.end(); 这个地方有坑,加了会关闭对话,看不到内容了
};
//如果路径是目录
if (!err && stats.isDirectory()) {
var html = " <head><meta charset = 'utf-8'/></head>";
//读取该路径下文件
fs.readdir(filePath, (err, files) => {
if (err) {
console.log("读取路径失败!");
} else {
// files.foreach(function (file) {
// //做成一个链接表,方便用户访问
// html+=`<div><a href="${file}">${file}</a></div>`;
// });
for (var file of files) {
if (file === "index.html") {
response.writeHead(200, { "content-type": "text/html" });
response.end(file);
break;
};
html += `<div><a href='${file}'>${file}</a></div>`;
console.log(html);
}
response.writeHead(200, { "content-type": "text/html" });
response.end(html);
};
});
};
});
};
module.exports = processRequest;
这里有几个细节的地方值得注意,一个是如何判断并实现重定向的,第二个是如何来判断content-type的。第三个要注意异步回调的执行顺序问题,有个坑就在传html那里。
再说一个小坑吧,response.end()这个方法我理解的不够深,这个方法是用来关闭对话或者向response的body部分传内容,我把它放在了stream操作的下面,结果可想而知... 所以再涉及到文件读写时,一定要注意这个地方。
下面是实现截图:
1.这是我的目录,不用管那个vscode,那是visual studio code文件配置目录
![1.png](http://upload-images.jianshu.io/upload_images/3373032-ffc04d2fc246ad97.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)
2.服务器启动在8080端口
![2.png](http://upload-images.jianshu.io/upload_images/3373032-9f44b9d0b7329c7a.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)
3.使用这个地址
![3.png](http://upload-images.jianshu.io/upload_images/3373032-18056b5020add779.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)
4.打开调试工具,可以发现Head部分是301,也就是发生了重定向
![4.png](http://upload-images.jianshu.io/upload_images/3373032-8bdef90683b18238.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)
5.来看看网页内容,可以看到,我们得到了该目录下可以访问的文件条目
![5.png](http://upload-images.jianshu.io/upload_images/3373032-cb9c9e1934c412ef.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)
6.最后,控制台信息
![6.png](http://upload-images.jianshu.io/upload_images/3373032-421376c5ef9f7914.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)
OK 就是这样。欢迎收看你的月亮我的心,好男人就是我~ 我们下周同一时间再会~
PS:如果本文有错误的地方请您一定要指出来,我会虚心接受,万分感谢。~~