Home  >  Article  >  Web Front-end  >  How to implement a static server using Node.js

How to implement a static server using Node.js

亚连
亚连Original
2018-06-02 14:55:241369browse

This article mainly introduces the implementation method of Node.js static server. It is very good and has reference value. Friends in need can refer to it

When you enter a url, this url may correspond to the server A resource (file) on may also correspond to a directory. So the server will analyze this URL and do different things for different situations. If this URL corresponds to a file, the server will return the file. If this URL corresponds to a folder, the server will return a list of all sub-files/sub-folders contained in this folder. The above is what a static server mainly does.

But the real situation is not as simple as that. The URL we get may be wrong. The file or folder it corresponds to may not exist at all, or some files and folders may not exist at all. What is protected by the system is hidden, and we don't want the client to know. Therefore, we have to make some different returns and prompts for these special situations.

Furthermore, before we actually return a file, we need to conduct some negotiations with the client. We need to know the language types, encoding methods, etc. that the client can accept in order to perform different return processing for different browsers. We need to tell the client some additional information about the returned file so that the client can better receive the data: Does the file need to be cached, and how should it be cached? Is the file compressed? How should it be decompressed? Wait...

At this point, we have a preliminary understanding of almost everything a static server mainly does, let's go!

Implementation

Project directory

static-server/
|
| - bin/
| | - start # 批处理文件
|  
|
| - src/
| | - App.js # main文件
| | - Config.js # 默认配置
|
|
·- package.json

##Configuration file

To start a server, we need to know the port number when the server is started and the working directory of the static server

let config = {
 host:'localhost' //提升用
 ,port:8080 //服务器启动时候的默认端口号
 ,path:path.resolve(__dirname,'..','test-dir') //静态服务器启动时默认的工作目录
}

Overall Framework

Note

This in the event function defaults to the bound object (here is the small server), which is modified here Server is a large object in order to call methods under Server in the callback function.

class Server(){
 constructor(options){
  /* === 合并配置参数 === */
  this.config = Object.assign({},config,options)
 }
 start(){
  /* === 启动http服务 === */
  let server = http.createServer();
  server.on('request',this.request.bind(this)); 
  server.listen(this.config.port,()=>{
   let url = `${this.config.host}:${this.config.port}`;
   console.log(`server started at ${chalk.green(url)}`)
  })
 }
 async request(req,res){
  /* === 处理客户端请求,决定响应信息 === */
  // try
  //如果是文件夹 -> 显示子文件、文件夹列表
  //如果是文件 -> sendFile()
  // catch
  //出错 -> sendError()
 }
 sendFile(){
  //对要返回的文件进行预处理并发送文件
 }
 handleCache(){
  //获取和设置缓存相关信息
 }
 getEncoding(){
  //获取和设置编码相关信息
 }
 getStream(){
  //获取和设置分块传输相关信息
 }
 sendError(){
  //错误提示
 }
}
module.exports = Server;

request request processing

Get the pathname of the url and work locally on the server Splice the root directory address and return a filename. Use the filename and stat methods to detect whether it is a file or a folder.

is a folder. Use the readdir method to return the list under the folder and wrap the list into an array of objects. Then combine the handlebar to compile the array data into the template, and finally return the template to the client

is the file, pass req, res, statObj, filepath to sendFile, and then hand it over to sendFile for processing

async request(req,res){
 let pathname = url.parse(req.url);
 if(pathname == '/favicon.ico') return;
 let filepath = path.join(this.config.root,pathname);
 try{
  let statObj = await stat(filepath);
  if(statObj.isDirectory()){
   let files = awaity readdir(filepath);
   files.map(file=>{
    name:file
    ,path:path.join(pathname,file)
   });
   // 让handlebar 拿着数去编译模板
   let html = this.list({
    title:pathname
    ,files
   })
   res.setHeader('Content-Type','text/html');
   res.end(html);
  }else{
   this.sendFile(req,res,filepath,statObj);
  }
 }catch(e){
  this.sendError(e,req,res);
 }
}

[tip] We will make the request method async, so that we can write asynchronous

method## just like writing synchronous code

#sendFile

Involves functions such as caching, encoding, and segmented transmission

sendFile(){
 if(this.handleCache(req,res,filepath,statObj)) return; //如果走缓存,则直接返回。
 res.setHeader('Content-type',mime.getType(filepath)+';charset=utf-8');
 let encoding = this.getEncoding(req,res); //获取浏览器能接收的编码并选择一种
 let rs = this.getStream(req,res,filepath,statObj); //支持断点续传
 if(encoding){
  rs.pipe(encoding).pipe(res);
 }else{
  rs.pipe(res);
 }
}

handleCache

What should be noted when processing cache is that cache is divided into forced cache and comparative cache, and the priority of forced cache is higher than relative cache. In other words, when the forced cache is in effect, the relative cache will not be used and the server will not initiate a request. But once the forced cache fails, the relative cache will be used. If the file identifier has not changed, the relative cache will take effect, and the client will still cache the data to get the data, so the forced cache and relative cache do not conflict. When forced caching and relative caching are used together, it can reduce the pressure on the server while keeping the requested data updated in a timely manner.

Another thing to note is that if two relative cache file identifiers are set at the same time, the cache will not take effect until both of them have not changed.

handleCache(req,res,filepath,statObj){
 let ifModifiedSince = req.headers['if-modified-since']; //第一次请求是不会有的
 let isNoneMatch = req.headers['is-none-match'];
 res.setHeader('Cache-Control','private,max-age=30');
 res.setHeader('Expires',new Date(Date.now()+30*1000).toGMTString()); //此时间必须为GMT
 
 let etag = statObj.size;
 let lastModified = statObj.ctime.toGMTString(); //此时间格式可配置
 res.setHeader('Etag',etag);
 res.setHeader('Last-Modified',lastModified);
 
 if(isNoneMatch && isNoneMatch != etag) return false; //若是第一次请求已经返回false
 if(ifModifiedSince && ifModifiedSince != lastModified) return false;
 if(isNoneMatch || ifModifiedSince){
 // 说明设置了isNoneMatch或则isModifiedSince且文件没有改变
  res.writeHead(304);
  res.end();
  return true;
 }esle{
  return false;
 }
}

getEncoding

Get the encoding type that the browser can accept from the request header and use regular matching Match the first one, create the corresponding zlib instance and return it to the sendFile method so that it can be encoded when returning the file.

getEncoding(req,res){
 let acceptEncoding = req.headers['accept-encoding'];
 if(/\bgzip\b/.test(acceptEncoding)){
  res.setHeader('Content-Encoding','gzip');
  return zlib.createGzip();
 }else if(/\bdeflate\b/.test(acceptEncoding)){
  res.setHeader('Content-Encoding','deflate');
  return zlib.createDeflate();
 }else{
  return null;
 }
}

getStream

Segmented transmission mainly uses the

req.headers in the request header ['range']

to confirm where the file to be received starts and ends. However, this part of the data is actually read through fs.createReadStream .

getStream(req,res,filepath,statObj){
 let start = 0;
 let end = startObj.size - 1;
 let range = req.headers['range'];
 if(range){
  res.setHeader('Accept-Range','bytes');
  res.statusCode = 206; //返回整个数据的一块
  let result = range.match(/bytes = (\d*)-(\d*)/); //不可能有小数,网络传输的最小单位为一个字节
  if(result){
   start = isNaN(result[1])?0:parseInt(result[1]);
   end = isNaN(result[2])?end:parseInt(result[2])-1; //因为readstream的索引是包前又包后故要减去1
  }
 }
 return fs.createReadStream(filepath,{
  start,end
 });
}

Packaged into a command line toolWe can enter it in the command line like

npm start

Start a dev-server Customize a startup command to start our static server. <p>The general idea of ​​​​implementation is: Configure a startup command and the path of the file that executes this command under the bin attribute in packge.json. Then we need to prepare a batch file, introduce our static server file into the file, let our server run, and then node link this file. </p> <p>The above is what I compiled for everyone. I hope it will be helpful to everyone in the future. </p> <p>Related articles: </p> <p><a href="http://www.php.cn/js-tutorial-399203.html" target="_blank">How to install style/css loader in vue2.0</a><br></p> <p>##Vue2.0 event broadcast and Receive (observer mode)<a href="http://www.php.cn/js-tutorial-399204.html" target="_blank"></a><br></p> <p>vue project internationalization vue-i18n installation and use tutorial<a href="http://www.php.cn/js-tutorial-399205.html" target="_blank"></a><br></p> <p></p> <p class="clearfix"><span class="jbTestPos"></span></p>

The above is the detailed content of How to implement a static server using Node.js. For more information, please follow other related articles on the PHP Chinese website!

Statement:
The content of this article is voluntarily contributed by netizens, and the copyright belongs to the original author. This site does not assume corresponding legal responsibility. If you find any content suspected of plagiarism or infringement, please contact admin@php.cn