background
Learning server side knowledge, entry is to mount the file to the server, we can access the corresponding file. In local development, we often put files on the server to access the same files in the same LAN through the same server address. For example, we use Xampp and sublime-Server for sulime. This article through node, handwritten a static resource server, so that you can define any folder as the root directory, to access the corresponding files, to achieve anywhere is your static-server.
Main Functions
- Reading a static file
- Static resource caching
- Resources compression
- MIME type support
- Breakpoint continuingly
- Published as an executable command and can run in the background, can be installed by NPM install -g
Useage
//install
$ npm i st-server -g
//forhelp
$ st-server -h
//start
$ st-server
// or with port
$ st-server -p 8800
// or with hostname
$ st-server -o localhost -p 8888
// or with folder
$ st-server -d /
// full parameters
$ st-server -d / -p 9900 -o localhost
Copy the code
-d stands for the root directory you want to access, -p stands for the port number (the same port number is not supported for multiple times, you need to manually kill the previous process), and -o stands for hostname. All source code has been uploaded to Github.
Source code analysis
- The entire code is implemented based on a StaticServer class, which first introduces all the configuration in the constructor, argv is the parameter that is typed in from the command line, and then gets the template that needs to be compiled, which simply displays a list of all the files in a folder. Based on Handlebars. The service is then started, listening for the request, which is handled by this.request()
class StaticServer{
constructor(argv){
this.config = Object.assign({},config,argv);
this.compileTpl = compileTpl();
}
startServer() {let server = http.createServer();
server.on('request',this.request.bind(this));
server.listen(this.config.port,()=>{
let serverUrl = `http://${this.config.host}:${this.config.port}`; Debug (' The service is enabled and the address is${chalk.green(serverUrl)}`); }}})Copy the code
- The main line is to read the address of the static service that you want to set up. If it is a folder, check whether there is an index. HTML file in the folder. If it is a file, the file content is displayed directly. The main premise in the display of the specific file, to determine whether there is a cache, a direct access to the cache, if not, then request the server.
async request(req,res){
let {pathname} = url.parse(req.url);
if(pathname == '/favicon.ico') {return this.sendError('NOT FOUND',req,res); } // Get the file directory to readlet filePath = path.join(this.config.root,pathname);
let statObj = await fsStat(filePath);
if(statObj.isdirectory ()){// List the contents of the directory if it is a directorylet files = await readDir(filePath);
let isHasIndexHtml = false;
files = files.map(file=>{
if(file.indexOf('index.html')>-1){
isHasIndexHtml = true;
}
return {
name:file,
url:path.join(pathname,file)
}
})
if(isHasIndexHtml){
let statObjN = await fsStat(filePath+'/index.html');
return this.sendFile(req,res,filePath+'/index.html'.statObjN);
}
let resHtml = this.compileTpl({
title:filePath,
files
})
res.setHeader('Content-Type'.'text/html');
res.end(resHtml);
}else{
this.sendFile(req,res,filePath,statObj);
}
}
sendFile(req,res,filePath,statObj){// Check whether the cache is removedif (this.getFileFromCache(req, res, statObj)) return; // Return res.setheader ('Content-Type',mime.getType(filePath)+'; charset=utf-8');
letencoding = this.getEncoding(req,res); // A common readable streamlet rs = this.getPartStream(req,res,filePath,statObj);
if(encoding){
rs.pipe(encoding).pipe(res);
}else{ rs.pipe(res); }}Copy the code
The sendFile method is the way to output content to the browser, including the following important points:
- Cache handling
getFileFromCache(req,res,statObj){
let ifModifiedSince = req.headers['if-modified-since'];
let isNoneMatch = req.headers['if-none-match'];
res.setHeader('Cache-Control'.'private,max-age=60');
res.setHeader('Expires',new Date(Date.now() + 60*1000).toUTCString());
let etag = crypto.createHash('sha1').update(statObj.ctime.toUTCString() + statObj.size).digest('hex');
let lastModified = statObj.ctime.toGMTString();
res.setHeader('ETag', etag);
res.setHeader('Last-Modified', lastModified);
if(isNoneMatch && isNoneMatch ! = etag) {return false;
}
if (ifModifiedSince && ifModifiedSince ! = lastModified) {return false;
}
if (isNoneMatch || ifModifiedSince) {
res.statusCode = 304;
res.end(' ');
return true;
} else {
return false; }}Copy the code
Here we implement negotiated caching through Last-Modified, ETag, and cache-Control, Expires, which only takes effect when all the Cache conditions are valid. The last-Modified principle determines whether a file has been Modified based on the modification time of the file. The ETag determines whether a file has been Modified based on the encryption of the file content. Cache-control, Expire Strong caching over time. 2. Compress files to reduce the size, speed up transmission and save bandwidth. Gzip and Deflate are supported here, and are processed using the Node module Zlib.
getEncoding(req,res){
let acceptEncoding = req.headers['accept-encoding'];
if(acceptEncoding.match(/\bgzip\b/)){
res.setHeader('Content-Encoding'.'gzip');
return zlib.createGzip();
}else if(acceptEncoding.match(/\bdeflate\b/)){
res.setHeader('Conetnt-Encoding'.'deflate');
return zlib.createDeflate();
}else{
returnnull; }}Copy the code
- Through range, resumable processing of breakpoints
getPartStream(req,res,filePath,statObj){
let start = 0;
let end = statObj.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]) ? start : parseInt(result[1]); end = isNaN(result[2]) ? end : parseInt(result[2]) - 1; }}return fs.createReadStream(filePath,{
start,end
})
}
Copy the code
- Generate a command line tool, install the yargs package with NPM, and add “bin”: {” st-server “: “Bin/WWW”}, point to the file that needs to execute the command, and then configure the corresponding command in the WWW, and start the subprocess for the main code operation, in order to solve the problem after you start the command, the command line has been stuck. Starting child processes is also supported by node’s native child_process module.
#! /usr/bin/env node
let yargs = require('yargs');
let argv = yargs.option('d', {
alias: 'root',
demand: 'false'.type: 'string',
default: process.cwd(),
description: 'Static file root'
}).option('o', {
alias: 'host',
demand: 'false',
default: 'localhost'.type: 'string',
description: 'Please configure the host to listen to'
}).option('p', {
alias: 'port',
demand: 'false'.type: 'number',
default: 8800,
description: 'Please configure the port number'
})
.usage('st-server [options]')
.example(
'st-server -d / -p 9900 -o localhost'.'Listen for client requests on port 9900 of this machine'
).help('h').argv;
let path = require('path');
let {
spawn
} = require('child_process');
let p1 = spawn('node'['www.js', JSON.stringify(argv)], {
cwd: __dirname
});
p1.unref();
process.exit(0);
Copy the code
reference
anywhere