手机版

节点搭建一个静态资源服务器的实现

时间:2021-08-23 来源:互联网 编辑:宝哥软件园 浏览:

使用结节的内置模块,创建一个可以访问目录的静态资源服务器,支持满量程文件读取,资源压缩与缓存等。

一、创建超文本传送协议服务器服务器

结节的超文本传送协议(超文本传输协议的缩写)模块提供超文本传送协议服务器和客户端接口,通过要求(“http”)使用。

先创建一个简单的超文本传送协议(Hyper Text Transport Protocol的缩写)服务器。配置参数如下:

//服务器/配置。js模块。导出={ root :进程。CWD(),主机: '127.0.0.1 ',端口: ' 8877 ' }进程方法返回Node.js进程的当前工作目录,和莱纳斯(男性)命令显示当前工作目录功能一样,

结节服务器每次收到超文本传送协议请求后都会调用http.createServer()这个回调函数,每次收一条请求,都会先解析请求头作为新的请求的一部分,然后用新的请求和作出反应对象触发回调函数。以下创建一个简单的超文本传送协议(超文本传输协议的缩写)服务,先默认响应的状态为200:

//服务器/http。jsconst http=require(' http ')const path=require(' path ')const config=require(' ./config)const server=http。createserver((请求,响应)={让文件路径=路径。加入(配置。根,请求。URL)响应。statuscode=200响应。setheader(' content-type ',' text/html ')响应。写(` html正文h1 hello World!/h1p $ { filePath }/p/body/html `)响应。end()})服务器。听(配置。port,config.host,()={ const addr=` http://$ { config。主机} : $ { config。端口} `控制台。信息(`服务器从${addr} `)开始)})客户端请求静态资源的地址可以通过request.url获得,然后使用小路模块拼接资源的路径。

执行$ node server/http.js后访问http://127.0.0.1 :8877/后的任意地址都会显示该路径:

每次修改服务器响应内容,都需要重新启动服务器更新,推荐自动监视更新自动重启的插件主管,使用监督者启动服务器。

$ npm安装主管-第纳尔主管服务器/http.js二、使用满量程读取资源文件

我们的目的是搭建一个静态资源服务器,当访问一个到资源文件或目录时,我们希望可以得到它。这时就需要使用结节内置的满量程模块读取静态资源文件,

使用fs.stat()读取文件状态信息,通过回调中的状态stats.isFile()判断文件还是目录,并使用fs.readdir()读取目录中的文件名

//服务器/路由。jsconst fs=require(' fs ')模块。exports=function(request,response,filePath){ fs.stat(filePath),(err,stats)={ if(err){ response。状态代码=404响应。setheader(' content-type ',' text/plain ')响应。end(`$ { filePath }不是文件`)返回} if(stats。isfile()){ response。statuscode=200响应。setheader(' content-type ',' text/plain ')fs。createreadstream(文件路径).管道(响应)}否则如果(统计。isdirectory()){ fs。readdir(filePath),(err,files)={ response。statuscode=200响应。setheader(' content-type ',' text/plain ')响应。end(文件。join(','))}) } }其中fs.createReadStream()读取文件流,管道()是分段读取文件到内存,优化高并发的情况。

修改之前的超文本传送协议(Hyper Text Transport Protocol的缩写)服务器,引入上面新建的route.js作为响应函数:

//服务器/http。jsconst http=require(' http ')const path=require(' path ')const config=require(' ./config)常量路由=require(' ./route)const server=http。createserver((请求,响应)={ let filePath=path。加入(配置。root,request.url)路由(request,response,filePath)})服务器。听(配置。port,config.host,()={ const addr=` http://$ { config。主机} 3: $ { config。端口} `控制台。信息(`服务器从$ { addr } `)开始)})再次执行$ node server/http.js如果是文件夹则显示目录:

如果是文件则直接输出:

成熟的静态资源服务器任何地方,深入理解开发作者写的。

三、util.promisify优化满量程异步

我们注意到fs.stat()和fs.readdir()都有回收回调。我们结合结节的util.promisify()来链式操作,代替地狱回调。

util.promisify只是返回一个承诺实例来方便异步操作,并且可以和异步/等待配合使用,修改route.js中满量程操作相关的代码:

//服务器/路由。jsconst fs=require(' fs ')const util=require(' util ')const stat=util。promisify(fs。stat)const readdir=util。promisify(fs。readdir)模块。出口=异步函数(请求、响应、文件路径){ try { const stats=await stat(文件路径)if(stats。isfile()){ response。statuscode=200响应。setheader(' content-type ',' text/plain ')fs。创建读流(文件路径)。管道(响应)}否则如果(统计。isdirectory()){ const files=wait readdir(文件路径)响应。statuscode=200响应。setheader(' content-type ',' text/plain ')响应。end(文件。join(','))} } catch(err){ console。错误(错误)响应。statuscode=404响应。setheader(' content-type ',' text/plain ')响应。end(` $ {文件路径}不是文件`)} }因为fs.stat()和fs.readdir()都可能返回错误,所以使用试捕捕获。

使用异步时需注意,异步回调需要使用等待返回异步操作,不加等待返回的是一个答应我,而且等待必须在异步非同步(异步)里面使用。

四、添加模版引擎

从上面的例子是手工输入文件路径,然后返回资源文件。现在优化这个例子,将文件目录变成超文本标记语言的a链接,点击后返回文件资源。

在第一个例子中使用response.write()插入超文本标记语言标签,这种方式显然是不友好的。这时候就使用模版引擎做到拼接超文本标记语言。

常用的模版引擎有很多呃,呃,杰德,车把,这里的使用EJB:

npm i ejs新建一个模版src/template/index.ejs,和超文本标记语言文件很像:

!DOCTYPE html html lang=' en ' head meta charset=' UTF-8 '标题节点服务器/标题/head dy %文件。foreach(函数(名称){ % a href='./%=dir %/%=name % ' rel=' external no follow ' %=name %/ABR % })%/body/html再次修改route.js,添加部署模版并ejs.render(),在文件目录的代码中传递文件、目录等参数:

//服务器/路由。jsconst fs=require(' fs ')const util=require(' util ')const path=require(' path ')const ejs=require(' ejs ')const config=require(' ./config')//异步优化常量stat=util。promisify(fs。stat)const readdir=util。promisify(fs。readdir)//引入模版const TPlpath=路径。联接(_ _ dirname,'./src/模板/索引。ejs’)const sourse=fs。readfilesync(tplPath)//读出来的是buffermodule.exports=异步函数(请求、响应、文件路径){ try { const stats=await stat(文件路径)if(stats。isfile()){ response。statuscode=200 } else if(stats。isdirectory()){ const files=wait readdir(文件路径)回应。status code=200 setheader(' content-type ',' text/html ')//response。end(文件。join(',')const dir=路径。相对(配置。根,文件路径)//相对于根目录const data={ files,dir: dir?`${dir}` : '' //路径。亲戚可能返回空字符串()} const模板=ejs。渲染(源。tostring(),数据)响应。end(template)} } catch(err){ response。statuscode=404 } }重启动$ node server/http.js就可以看到文件目录的链接:

五、匹配文件哑剧类型

静态资源有图片、css、js、json、html等,

在上面判断stats.isFile()后响应头设置的内容类型都为文本/纯文本,但各种文件有不同的哑剧类型列表。

我们先根据文件的后缀匹配它的哑剧类型:

//服务器/mime。jsconst path=require(' path ')const mime type={ ' js ' : ' application/x-JavaScript ',' html': 'text/html ',' css': 'text/css ',' txt ' : ' text/plain ' }模块。exports=(FilePath)={ let ext=path。扩展名(文件路径).拆分('.').流行音乐.toLowerCase() //取扩展名if(!ext) { //如果没有扩展名,例如是文件ext=filePath }返回mime types[ext]| | mime types[' txt ']}匹配到文件的哑剧类型,再使用response.setHeader('内容类型,' XXX ')设置响应头:

//服务器/路由。jsconst mime=require(' ./mime ')if(stats。isFIle()){ const MiMe Type=MiMe(FilePath)响应。状态代码=200响应。SetHeader('内容类型',MiMe类型)fs。CreateReadStream(文件路径).管道(响应)运行计算机网络服务器服务器访问一个文件,可以看到内容类型修改了:

六、文件传输压缩

注意到请求标题中有接受—编码:gzip,放气,告诉服务器客户端所支持的压缩方式,响应时应答标题中使用内容编码标志文件的压缩方式。

结节内置数据压缩模块支持文件压缩。在前面文件读取使用的是fs.createReadStream(),所以压缩是对ReadStream文件流。示例gzip,放气方式的压缩:

最常用文件压缩,gzip等,使用,对于文件是用ReadStream文件流进行读取的,所以对ReadStream进行压缩:

//服务器/压缩。jsconst zlib=require(' zlib ')模块。导出=(readStream,request,response)={ const accept encoding=request。header[' accept-encoding ']if(!acceptEncoding ||!接受编码。匹配(/\ b(gzip | deflate)\ b/){ return readStream } else if(接受Encoding。匹配(/\ bgzip \ b/){响应。setheader('内容编码',' gzip ')返回readStream。管道(zlib。creategzip())} else if(接受编码。匹配(/\ bdeflate \ b/){响应。setheader('内容编码','放气')返回readStream。管道(zlib。createdeflate())} }修改route.js文件读取的代码:

//服务器/路由。jsconst compress=require(' ./compress ')if(stats。IsFIle()){ const MiMe TYPe=MiMe(文件路径)响应。状态代码=200响应。setheader(' Content-TYPe ',MiMe TYPe)//fs。CreateReadStream(文件路径).管道(响应)让readStream=fs.createReadStream(文件路径)如果(文件路径。match(config.compress)) { //正则匹配:/\.(html | js | CSS | MD)/readStream=compress(readStream,请求,响应)} readStream.pipe(响应)}运行计算机网络服务器可以看到不仅应答标题增加压缩标志,而且3K大小的资源压缩到了1K,效果明显:

七、资源缓存

以上的结节服务都是浏览器首次请求或无缓存状态下的,那如果浏览器/客户端请求过资源,一个重要的前端优化点就是缓存资源在客户端。缓存有强缓存和协商缓存:

强缓存在请求标题中的字段是期满和缓存控制;如果在有效期内则直接加载缓存资源,状态码直接是显示200。

协商缓存在请求标题中的字段是:

如果-修改-自(对应值为上次响应标题中的最后修改)如果-无-匹配(对应值为上次响应标题中的Etag)如果协商成功则返回304 状态码,更新过期时间并加载浏览器本地资源,否则返回服务器端资源文件。

首先配置默认的躲藏字段:

//服务器/配置。js模块。导出={ root :进程。CWD(),主机: '127.0.0.1 ',端口: '8877 ',压缩3360/\ .(html|js|css|md)/,cache: { maxAge: 2,expires: true,cacheControl: true,lastModified: true,etag: true }}新建server/cache.js,设置响应头:

const config=require(' ./config’)函数refreshRes (stats,response) { const {maxAge,expires,cacheControl,lastModified,etag }=config . cacheif(Expires){ response。setheader(' Expires ',(新日期(日期。现在)(maxAge * 1000)).tout string());} if(缓存控制){响应。setheader(' Cache-Control ',` public,max-age=$ { maxAge } `);} if(上次修改){回应。setheader('上次修改',统计信息。时间。tout string());} if(ETag){ response。setheader(' ETag ',` $ { stats。大小}-$ { stats。时间。tout string()} `);//mtime需要转成字符串,否则在窗子环境下会报错} }模块。exports=function isFresh(stats,request,response) { refreshRes(stats,response);const最后修改时间=请求。标题[' if-modified-after '];const etag=请求。标头[' if-none-match '];if(!lastModified!etag){ return false;} if (lastModified lastModified!==回应。getheader('上次修改'){ 0返回false} if (etag etag!==回应。GetHeader(' ETag '){ return false;}返回true };最后修改route.js中的

//服务器/路由。js const isCache=require(' ./cache ')if(stats。isFIle()){ const MiMe TYPe=MiMe(FilePath)响应。setHeader(' Content-TYPe ',mimeType) if (isCache(stats,request,response)){ response。StatusCode=304回应。end();返回;}回应。状态代码=200//fs。CreateReadStream(文件路径)。管道(响应)让readStream=fs.createReadStream(文件路径)如果(文件路径。匹配(配置。压缩){ readStream=压缩(readStream,请求,响应)} readStream.pipe(响应)重启节点服务器访问某个文件,在第一次请求成功时响应标题返回缓存时间:

一段时间后再次请求该资源文件,请求标题发送协商请求字段:

以上就是一个简单的结节静态资源服务器。希望对大家的学习有所帮助,也希望大家多多支持我们。

版权声明:节点搭建一个静态资源服务器的实现是由宝哥软件园云端程序自动收集整理而来。如果本文侵犯了你的权益,请联系本站底部QQ或者邮箱删除。