First let’s take a look at the static file server I want to write for you to achieve what functions, and then according to the specific functions we will introduce one by one
Supports the following functions
- Supports log debugging
- Reading a static file
- Read MIME type support for files
- The folder list shows the Handlebars template engine
- Cache support/control
- Range supports breakpoint continuation
- Supports Gzip and Deflate compression
- You can run the following command globally to set the port number and host name of the root directory: myselfsever -d Specifies the root directory of the static file -p specifies the port number -o specifies the listening host
Download address
Start the
NPM install // Install dependent NPM link // create a connection to myselfServer // Start the service in any directory // access localhost:8080Copy the code
Next, let’s take a look at the rough structure of our server
Next, let’s introduce our server
In order to implement a server function, we often need to set the server host name, port number, static file root directory and other information, here our config.js help us to implement this configuration, of course, this is the basic configuration, later we will talk about how to change the configuration through the command line. The basic configuration is as follows:
let path = require('path'); Let config = {host: 'localhost',// host port: 8080,// host port number root: path.resolve(__dirname, '.. ')// static file root} module.exports = config;Copy the code
After configuration, we need to write our service. The code of the service will be reflected in our app.js, which includes almost all the functions listed above. Let’s take a look at the code structure of app.js
const fs = require('fs');
const url = require('url');
const http = require('http');
const path = require('path');
const config = require('./config'); Class Server {constructor(argv) {constructor(argv) {this.list = list(); To override the basic parameter this.config = object. assign({}, config, argv)}start() {/* Start the HTTP service */let server = http.createServer();
server.on('request', this.request.bind(this));
let url = `http://${config.host}:${config.port}`; Server.listen (this.config.port, ()=>{// (1) Support log output DEBUG (' server started at '${chalk.green(url)}`); })} /* (2) async request(req,res) {// Try catch (req,res) { // try // folder: (2.1) Handlebars template engine: SendError} /* send file to browser*/ sendFile (req, res, filePath,statObj) {// (2.2) handle error*/ sendError (error, req, /* isCache (req, res, filePath, filePath, filePath, filePath, filePath, filePath, filePath, filePath)statObj) {// (3) handle cache} /*broken-point transferring-transferring-transfer (req, res, filePath,statObj) {// (4) support for continuation} /*compression */ compression (req, res) {// (5) support for gzip and deflate compression}} module. Exports = Server; (6) myselfsever-d-p Specifies the port number. -o Specifies the host to listen to. The script code implemented by this command is in the commond file in the bin directoryCopy the code
Enter the detailed analysis stage
Note: Every time you use a module, remember to install it locally in advance
(1) How to print error logs –debug
const debug = require('debug'); // Install debug package debug('staticserver:app'); // debug The third-party module returns a function that takes two arguments: the name of your current project (package.json) and the name of your module's service entry file. In our case, app.jsCopy the code
When we use the debug error output module in our project, we need to configure environment variables so that our debug logs will be output
How to configure environment variables Configure environment variables on Windows
- Configure a single file, that is, only the debug logs in the current file can be output. (Advantage: You can flexibly configure the output of the desired file debug logs.)
$ set DEBUG=staticserver:app
Copy the code
- After configuring the entire project, debug logs in the entire project are output
$ set DEBUG=staticserver:*
Copy the code
Configure environment variables on the MAC
$ export DEBUG=staticserver:app
$ export DEBUG=staticserver:*
Copy the code
(2) Read static files
To display a list of files if they are folders, we used the Handlebars template engine
const handlebars = require('handlebars'); // Call the handlebars.compile method to generate a templatefunction list () {
let tmpl = fs.readFileSync(path.resolve(__dirname,'template'.'list.html'),'utf8');
return handlebars.compile(tmpl)
}
Copy the code
Let’s look at the case where the access path is a file. In this case, we return different response types depending on the suffix of the file, which is where we use our MIME module
/* static file server */ const {promisify, inspect} = require('util');
const mime = require('mime');
const stat= promisify(fs.stat); const readdir = promisify(fs.readdir); Async Request (req,res) {// Obtain the desired path of the clientlet{ pathname } = url.parse(req.url); // An error message is returned if favicon.ico is accessedif (pathname == '/favicon.ico') {
return this.sendError('not favicon.ico',req,res)} // Get the path of the accessed file or folderletfilePath = path.join(this.config.root, pathname); Try {// Determine whether the access path is a folder or a filelet statObj = await stat(filePath);
if (statObj.isdirectory ()) {// If it is a directory, the list of files under the directory should be displayed otherwiselet files = await readdir(filePath);
files = files.map(file => ({
name: file,
url: path.join(pathname, file)
}))
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) { debug(inspect(e)); // Convert an object to a string, because some tosting generate object object this.sendError(e, req, res); }} // Let's look at the case where the access path is a file. The mime module sendFile (req, res, filePath,statObj) {// Remove the cache if it existsif(this.isCache(req, res, filePath, statObj)){
return; } res.statusCode = 200; // You can omit res.setheader ('Content-Type', mime.getType(filePath) + '; charset=utf-8');
letencoding = this.compression(req,res); // Does it need to be compressedifThis. rangeTransfer(req, res, filePath,statObj).pipe(encoding).pipe(res)
} else {
this.rangeTransfer(req, res, filePath, statObj).pipe(res)
}
}
Copy the code
(3) Cache support and control
To understand whether or not caching exists and works, you can take a look at the node caching mechanism in this article to further understand caching
IsCache (req, res, filePath,statObj) {
let ifNoneMatch = req.headers['if-none-match'];
let ifModifiedSince = req.headers['if-modified-since'];
res.setHeader('Cache-Control'.'private,max-age=10');
res.setHeader('Expires',new Date(Date.now() + 10*1000).toGMTString);
let etag = statObj.size;
let lastModified = statObj.ctime.toGMTString();
res.setHeader('Etag',etag)
res.setHeader('Last-Modified',lastModified);
if(ifNoneMatch && ifNoneMatch ! = etag) {return false
}
if(ifModifiedSince && ifModifiedSince ! = lastModified){return false
}
if(ifNoneMatch || ifModifiedSince) {
res.writeHead(304);
res.end();
return true
} else {
return false}}Copy the code
(4) Range support, resumable
This option specifies the range of bytes to download and is often applied to downloading files in chunks
Range can be expressed in various ways. For example, 100-500 specifies 400 bytes of data starting from 100. -500 indicates the last 500 bytes. 5000- represents all bytes starting with the 5000th byte
It is also possible to specify multiple byte blocks at the same time, separated by a comma
The server tells the client to use range response.setHeader(‘ accept-ranges ‘, ‘bytes’)
The Server uses Range:bytes=0-xxx in the request header to determine whether a Range request is being made. If the Range:bytes=0-xxx value exists and is valid, only the requested part of the file is sent back. The status code of the response is 206. Indicates Request Range Not Satisfiable
/* rangeTransfer (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// Returns a piece of the entire contentlet result = range.match(/bytes=(\d*)-(\d*)/);
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
(5) Support gZIP and Deflate compression
The server determines which decompression type the client supports based on the value in the req.headers[‘accept-encoding’] field
/*compression */ compression (req, res) {let acceptEncoding = req.headers['accept-encoding']; //163.comif(acceptEncoding) {
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
}
}
}
Copy the code
Myselfsever -d specifies the root directory of the static file. -p specifies the port number. -o specifies the host to be monitored
This functionality is implemented in the command file under our bin directory. #! At the beginning of the file The /usr/bin/env node command tells the script which program to use to execute it. See the node.js command line development tutorial
#! /usr/bin/env node
let yargs = require('yargs');
let Server = require('.. /src/app.js')
let argv = yargs.option('d', {
alias: 'root',
demand: false,
description: 'Please configure to listen to static file directories',
default: process.cwd(),
type: 'string'
}).option('o', {
alias: 'host',
demand: false,
description: 'Please configure the host to listen to',
default: 'localhost'.type: 'string'
}).option('p', {
alias: 'port',
demand: false,
description: 'Please configure the port number of the listening host',
default: 8080,
type: 'number'
}).usage('myselfsever -d / -p 8080 -o localhost')
.example(
'myselfsever -d / -p 9090 -o localhost'.'Listen for client requests on port 9090 of this machine'
).help('h').argv;
let server = new Server();
server.start(argv);
Copy the code
Make progress every day; Don’t be afraid of new things. Learn a little every day.