Node.js 系列-搭建静态资源服务器

  • 时间:2018-11-01 23:16 作者:前端嫣然呀呀呀 来源:前端嫣然呀呀呀 阅读:763
  • 扫一扫,手机访问
摘要:什么是静态资源服务器?那先说什么是 静态资源 , 它指的是 不会被服务器的动态运行所改变或者者生成的文件 . 它最初在服务器运行之前是什么样子, 到服务器结束运行时, 它还是那个样子. 比方平常写的 js , css , html 文件, 都可以算是静态资源. 那么很容易了解, 静态资源服务器的功能

什么是静态资源服务器?

那先说什么是 静态资源 , 它指的是 不会被服务器的动态运行所改变或者者生成的文件 . 它最初在服务器运行之前是什么样子, 到服务器结束运行时, 它还是那个样子. 比方平常写的 js , css , html 文件, 都可以算是静态资源. 那么很容易了解, 静态资源服务器的功能就是向用户端提供静态资源.

话不多说, 开始写代码:

首先我们知道, 它先是一个 "服务器". 那根据上一章的所学, 我们要先用 http 板块创立一个 HTTP 服务器.

var http = require('http');
var server = http.createServer(function(req, res) {
// 业务逻辑, 等会儿再写.
});
server.listen(3000, function() {
console.log("静态资源服务器运行中.");
console.log("正在监听 3000 端口:")
})

url 板块

url 板块 - 文档

有了 HTTP 服务器之后, 我们即可以获取从用户端发过来的 HTTP 请求了.

请求报文中包含着请求 URL. 前文说过, URL 用于定位网络上的资源. 用户端通过 URL 来指明想要的服务器上资源. 那么服务器为了搞清楚用户端究竟想要什么, 我们需要解决和解析 URL. 在 Node.js 中, 我们使用 url 板块来完成这类操作.

我们知道 URL 字符串是具备结构的字符串,包含多个意义不同的组成部分。 通过 url.parse() 函数, URL 字符串可以被解析为一个 URL 对象,其属性对应于字符串的各组成部分。如下图所示.

Node.js 系列-搭建静态资源服务器

那么回到我们的静态文件服务器代码.:

先在 http.createServer 函数被调用之前, 引入 url 板块:

var url = require('url');

而后在 HTTP 服务器里解析请求 URL. 用户端发来的请求 URL 作为属性存放在 http.createServer 的回调函数参数所接收的请求对象里, 属性名为 url .

var server = http.createServer(function(req, res) {
var urlObj = url.parse(req.url);
});

path 板块

path 板块 - 文档

接下来从解析后的 URL 对象 urlObj 里获得请求 URL 中的路径名(pathname). 路径名保存在 pathname 属性里.

var server = http.createServer(function(req, res) {
var urlObj = url.parse(req.url);
var urlPathname = urlObj.pathname;
});

但是光有 URL 对象里面的路径名是不够的. 我们还需要取得目标文件在服务器中所在目录的目录名(dirname).

如果说我们的项目结构是下面这样的:

.
├── public
│ ├── index.css
│ └── index.html
└── server.js

我们的服务器代码写在 server.js 文件里. 用户端想要请求保存在 public 目录里的 index.html 文件. 客户在浏览器中输入 URL 的时候, 他只知道他想要的文件叫 index.html , 但这个文件在 HTTP 服务器所在的设施中的 『 绝对位置 』是不被知道的. 所以我们需要让 HTTP 服务器自己去解决这部分操作.

在这里就需要使用 Node.js 自带的 path 板块. 其提供了少量工具函数,用于解决文件与目录的路径.

使用起来很简单, 首先还是在 http.createServer 函数被调用之前, 引入 path 板块:

var path = require('path');

之后我们用 path.join 这个方法来把 目标文件所在目录的目录名和请求 URL 中的路径名合并起来. 在这个例子中, 用户端可以访问的静态文件一律在 public 这个目录中, 而 public 目录又在 server.js 文件所在的目录中. server.js 中保存的是我们的服务器代码.

想要取得 server.js 所在目录的在整个设施中的绝对路径, 我们可以在服务器代码中调用变量 __dirname , 它是当前文件在被板块包装器包装时传入的变量, 保存了当前板块的目录名。

var server = http.createServer(function(req, res) {

var urlObj = url.parse(req.url);

var urlPathname = urlObj.pathname;

var filePathname = path.join(__dirname, "/public", urlPathname);

});

假如你想的话, 你可以用 console.log(filePathname) 来看看服务器运行后, 从用户端收到的请求 URL 会被转换成什么样.

fs 板块

fs 板块 - 文档

现在来到了最重要的一步, 读取目标文件, 并且返回文件给用户端.

我们需要用 Node.js 自带的 fs 板块中的 fs.write 方法来实现这一步. 该方法第一个参数为目标文件的路径, 最后一个参数为一个回调函数, 回调有两个参数 (err, data),其中 data 是文件的内容, 假如发生错误的话 err 保存错误信息. fs.write 方法可以在第二个参数中指定字符编码, 假如未指定则返回原始的 buffer. 在这个例子中, 我们不考虑这一项.

那么具体代码如下:

首先引入 fs 板块, 我就不赘述了, 参照前面即可以了. 下面是读取文件的代码.

var server = http.createServer(function(req, res) {
var urlObj = url.parse(req.url);
var urlPathname = urlObj.pathname;
var filePathname = path.join(__dirname, "/public", urlPathname);

fs.readFile(filePathname, (err, data) => {
// 假如有问题返回 404
if (err) {
res.writeHead(404);
res.write("404 - File is not found!");
res.end();
// 没问题返回文件内容
} else {
res.writeHead(200);
res.write(data);
res.end();
}
})
});

现在我们就实现了一个基本的『 静态文件服务器 』可以在允许用户端请求保存在服务器中公开的静态文件了. 你可以尝试启动服务器, 而后让浏览器中访问 http://localhost:3000/index.html. 我的效果如下:

Node.js 系列-搭建静态资源服务器

设置 MIME 类型

MIME 文档 - MDN Content-Type 文档 - MDN

多用途 Internet 邮件扩展(MIME)类型是用一种标准化的方式来表示文档的 "性质" 和 "格式"。 简单说, 浏览器通过 MIME 类型来确定如何解决文档. 因而在响应对象的头部设置正确 MIME 类型是非常重要的.

MIME 的组成结构非常简单: 由类型与子类型两个字符串中间用 '/' 分隔而组成, 其中没有空格. MIME 类型对大小写不敏感,但是传统写法都是小写.

例如:

text/plain
text/html
image/png

在服务器中, 我们通过设置 Content-Type 这个响应头部的值, 来指示响应回去的资源的 MIME 类型. 在 Node.js 中, 可以很方便的用响应对象的 writeHead 方法来设置响应状态码和响应头部.

如果我们要响应给用户端一个 HTML 文件, 那么我们应该使用下面这条代码:

res.writeHead(200, {"Content-Type":"text/html"});

你会发现我在上面的静态资源服务器的代码中, 没有设置响应资源的 MIME 类型. 但假如你试着运行服务器的话, 你会发现静态资源也以正确方式被展现到了浏览器.

之所以会这样的起因是在缺失 MIME 类型或者用户端认为文件设置了错误的 MIME 类型时,浏览器可能会通过查看资源来进行猜测 MIME 类型, 叫做 『 MIME 嗅探 』. 不同的浏览器在不同的情况下可能会执行不同的操作。所以为了保证资源在每一个浏览器下的行为一致性, 我们需要手动设置 MIME 类型.

那么首先我们需要获取到准备响应给用户端的文件的 后缀名 .

要做到这一步我们需要使用 path 板块的 parse 方法. 这个方法可以将一段路径解析成一个对象, 其中的属性对应路径的各个部位.

继续再刚才静态文件服务器案例的代码上增加:

var server = http.createServer(function(req, res) {
var urlObj = url.parse(req.url);
var urlPathname = urlObj.pathname;
var filePathname = path.join(__dirname, "/public", urlPathname);

// 解析后对象的 ext 属性中保存着目标文件的后缀名
var ext = path.parse(urlPathname).ext;

// 读取文件的代码...

获取了文件后缀之后, 我们需要查找其对应的 MIME 类型 了. 这一步可以很轻松的使用第三方板块MIME 来实现. 你可以自行去 NPM 上去查阅它的使用文档.

对于我们目前的需求来说, 只要要用到 MIME 板块的 getType() 方法. 这个方法接收一个字符串参数 (后缀名), 返回其对应的 MIME 类型, 假如没有就返回 null .

使用的话, 首先要用 npm 安装 MIME 板块 ( 假如你还没创立 package.json 文件的话, 别忘了先执行 npm init )

npm install mime --save

安装完毕. 引入板块到服务器代码中, 而后我们就直接用刚刚取得的后缀去找到其对应的 MIME 类型

var mime = require('mime');
var server = http.createServer(function(req, res) {
var urlObj = url.parse(req.url);
var urlPathname = urlObj.pathname;
var filePathname = path.join(__dirname, "/public", urlPathname);

// 解析后对象的 ext 属性中保存着目标文件的后缀名
var ext = path.parse(urlPathname).ext;
// 获取后缀对应的 MIME 类型
var mimeType = mime.getType(ext);

// 读取文件的代码...
});

好了, 现在最重要的东西 MIME 类型我们已经得到了. 接下来只需在响应对象的 writeHead 方法里设置好 Content-Type 就行了.

var server = http.createServer(function(req, res) {
// 代码省略...
var mimeType = mime.getType(ext);
fs.readFile(filePathname, (err, data) => {
// 假如有问题返回 404
if (err) {
res.writeHead(404, { "Content-Type": "text/plain" });
res.write("404 - File is not found!");
res.end();
// 没问题返回文件内容
} else {
// 设置好响应头
res.writeHead(200, { "Content-Type": mimeType });
res.write(data);
res.end();
}
})
});

阶段性胜利 :v: 现在运行服务器, 在浏览器里访问一下 localhost:3000/index.html 试试吧!

Node.js 系列-搭建静态资源服务器

可以看到现在 Content-Type 已经被正确设置了!

重构代码

现在来看看你的代码, 是不是开始感觉有点乱糟糟的. 我想聪明的你已经发现, 整个静态文件服务器的代码就是在做一件事: 响应回用户端想要的静态文件. 这段代码职责单一, 且复用频率很高. 那么我们有理由将其封装成一个板块.

具体的过程我就不赘述了. 以下是我的板块代码:

// readStaticFile.js
// 引入依赖的板块
var path = require('path');
var fs = require('fs');
var mime = require('mime');
function readStaticFile(res, filePathname) {
var ext = path.parse(filePathname).ext;
var mimeType = mime.getType(ext);

// 判断路径能否有后缀, 有的话则说明用户端要请求的是一个文件
if (ext) {
// 根据传入的目标文件路径来读取对应文件
fs.readFile(filePathname, (err, data) => {
// 错误解决
if (err) {
res.writeHead(404, { "Content-Type": "text/plain" });
res.write("404 - NOT FOUND");
res.end();
} else {
res.writeHead(200, { "Content-Type": mimeType });
res.write(data);
res.end();
}
});
// 返回 false 表示, 用户端想要的 是 静态文件
return true;
} else {
// 返回 false 表示, 用户端想要的 不是 静态文件
return false;
}
}
// 导出函数
module.exports = readStaticFile;

用于读取静态文件的板块 readStaticFile 封装好了之后. 我们可以在项目目录里新建一个 modules 目录, 用于存放板块. 以下是我目前的项目结构.

Node.js 系列-搭建静态资源服务器

封装好了板块之后, 我们即可以删去服务器代码里那段读取文件的代码了, 直接引用板块就行了. 以下是我修改后的 server.js 代码:

// server.js 
// 引入相关板块
var http = require('http');
var url = require('url');
var path = require('path');
var readStaticFile = require('./modules/readStaticFile');
// 搭建 HTTP 服务器
var server = http.createServer(function(req, res) {
var urlObj = url.parse(req.url);
var urlPathname = urlObj.pathname;
var filePathname = path.join(__dirname, "/public", urlPathname);

// 读取静态文件
readStaticFile(res, filePathname);
});
// 在 3000 端口监听请求
server.listen(3000, function() {
console.log("服务器运行中.");
console.log("正在监听 3000 端口:")
})

:laughing: 好啦,今天的分享就告一段落啦。下一篇中,我会详情 "如何搭建服务器路由" 和 "解决浏览器表单提交"

传送门 - Node.js 系列 - 搭建路由 & 解决表单提交

  • 全部评论(0)
最新发布的资讯信息
【系统环境|】2FA验证器 验证码如何登录(2024-04-01 20:18)
【系统环境|】怎么做才能建设好外贸网站?(2023-12-20 10:05)
【系统环境|数据库】 潮玩宇宙游戏道具收集方法(2023-12-12 16:13)
【系统环境|】遥遥领先!青否数字人直播系统5.0发布,支持真人接管实时驱动!(2023-10-12 17:31)
【系统环境|服务器应用】克隆自己的数字人形象需要几步?(2023-09-20 17:13)
【系统环境|】Tiktok登录教程(2023-02-13 14:17)
【系统环境|】ZORRO佐罗软件安装教程及一键新机使用方法详细简介(2023-02-10 21:56)
【系统环境|】阿里云 centos 云盘扩容命令(2023-01-10 16:35)
【系统环境|】补单系统搭建补单源码搭建(2022-05-18 11:35)
【系统环境|服务器应用】高端显卡再度登上热搜,竟然是因为“断崖式”的降价(2022-04-12 19:47)
手机二维码手机访问领取大礼包
返回顶部