This article is participating in node.js advanced technology essay, click to see details.

Serve-static is middleware that provides static file hosting services in Node, behind which is middleware encapsulation of the SEND library. The library maps to the resource based on the requested req.URL and does not respond directly to 404 when the resource does not exist, but instead calls next() to move on to the next middleware. NodeJs static file hosting service implementation principle of this article.

Brothers, I don’t know if you can stand it or not!! Ding in!! Ding in!! Top in!!

Without further ado, let’s start with the basics.

The basic use

  • Use a static file hosting service in an HTTP service
const http = require('http');
const finalhandler = require('finalhandler');
const serveStatic = require('serve-static');

const root = __dirname + '/static';
const fileServer = serveStatic(root, {
  index: ['index.html'.'index.htm']});const server = http.createServer((req, res) = > {
  const done = finalhandler(req, res);
  fileServer(req, res, done);
});

server.listen(3200.() = > {
  console.log('[file server] running at port 3200.');
});
Copy the code

Create static/index.html file in curl directory:

$curl = $curl = $curl = $curl = $curl//localhost:3200
Copy the code
  • Demonstrates an example of providing resource downloads in an HTTP service
const http = require('http');
const contentDisposition = require('content-disposition');
const finalhandler = require('finalhandler');
const serveStatic = require('serve-static');

// Initialize the file download service
const root = __dirname + '/static';
const fileServer = serveStatic(root, {
  index: false.setHeaders: setHeaders,
});

// Set the response header to force the download
function setHeaders(res, path) {
  res.setHeader('Content-Disposition', contentDisposition(path));
}

// Initialize the HTTP service
const server = http.createServer((req, res) = > {
  const done = finalhandler(req, res);
  fileServer(req, res, done);
});

server.listen(3200.() = > {
  console.log('[file server] running at port 3200.');
});
Copy the code

Curl = curl = curl = curl = curl = curl

# will servestaticCurl -o./download. HTML HTTP: /download.//localhost:3200/index.html
Copy the code
  • Used as Express middleware
const express = require('express');
const serveStatic = require('serve-static');

const root = __dirname + '/static';
const fileServer = serveStatic(root, {
  index: ['index.html'.'index.htm']});const app = new express();

app.use(fileServer);

app.listen(3200.() = > {
  console.log('[koa file server] running at port 3200.');
});
Copy the code

Source code analysis

The send library is implemented as an index.js file in the root directory. The core structure is to export a function:

// the index.js file in the root directory
'use strict'

var send = require('send')

/**
 * Module exports.
 * @public* /

module.exports = serveStatic
module.exports.mime = send.mime

function serveStatic (root, options) {}
Copy the code

Let’s look at the implementation of serveStatic:

function serveStatic (root, options) {
  // The root path must be specified
  if(! root) {throw new TypeError('root path required')}// Type check for root path values
  if (typeofroot ! = ='string') {
    throw new TypeError('root path must be a string')}// copy Parameters passed by the user
  var opts = Object.create(options || null)

  // fall-though The default value is true
  varfallthrough = opts.fallthrough ! = =false

  // default redirect The default value is true
  varredirect = opts.redirect ! = =false

  // headers listener
  var setHeaders = opts.setHeaders

  if (setHeaders && typeofsetHeaders ! = ='function') {
    throw new TypeError('option setHeaders must be function')}// setup options for send
  opts.maxage = opts.maxage || opts.maxAge || 0
  opts.root = resolve(root)
  
  // send a handler for the directory event
  // Is used for further redirection when processing path-time file options
  // This event allows users to customize folder path jump logic
  var onDirectory = redirect
    ? createRedirectDirectoryListener()
    : createNotFoundDirectoryListener()
  
  // Returns the middleware function
  return function serveStatic (req, res, next) {}
  / /...
}
Copy the code

This function first initializes some default parameters and then returns a middleware formatted function. There is nothing to say here, but one small tip to mention is the initialization technique for Boolean defaults:

// Set the default value to true
The default value is true as long as the user does not display the declaration argument as false
varfallthrough = opts.fallthrough ! = =false

// Set the default value to false
The default value is false as long as the user does not display the declaration parameter as true
var redirect = opts.redirect === true
Copy the code

Let’s take a closer look at the returned middleware function:

// Return middleware
return function serveStatic (req, res, next) {
  // Processing requests is not a GET or HEAD scenario
  if(req.method ! = ='GET'&& req.method ! = ='HEAD') {
    // If fallThrough is true, the next middleware is executed directly next
    if (fallthrough) {
      return next()
    }

    // Otherwise respond directly to the 405 status notification that only GET or HEAD requests are allowed
    res.statusCode = 405
    res.setHeader('Allow'.'GET, HEAD')
    res.setHeader('Content-Length'.'0')
    res.end()
    return
  }

  varforwardError = ! fallthroughvar originalUrl = parseUrl.original(req)
  // Get the pathName path
  var path = parseUrl(req).pathname

  // make sure redirect occurs at mount
  if (path === '/' && originalUrl.pathname.substr(-1)! = ='/') {
    path = ' '
  }

  // instantiate send to get the flow
  var stream = send(req, path, opts)

  // Add processing logic for folder resources
  stream.on('directory', onDirectory)

  // If the user sets setHeaders, customize the response header function
  if (setHeaders) {
    stream.on('headers', setHeaders)
  }

  // add file listener for fallthrough
  if (fallthrough) {
    stream.on('file'.function onFile () {
      // If it is a read file, set this variable to true
      // This variable is used to check whether the file reads next error
      forwardError = true})}// Listen for the wrong hook
  stream.on('error'.function error (err) {
    // If the user allows next error logic, or the error status code is greater than or equal to 500
    // Next error
    if(forwardError || ! (err.statusCode <500)) {
      next(err)
      return
    }

    next()
  })

  // Connect the stream to the RES stream, i.e. HTTP returns stream data
  stream.pipe(res)
}
Copy the code

The main logic of this part:

  • The nonGET | HEADThe request of
    • This parameter is determined based on the configuration parametersnext()Or response405error
  • instantiationsendgetsendThe instancestreamflow
  • addsendThe instancedirectoryThe event
    • Determine redirection or response based on configuration parameters404error
    • sendThe library of the defaultdirectoryLogic is the response403error
  • Add headers events for send instances to allow users to customize the response headers
  • Add error handling events for send instances
    • If it is a file outflow error directlynext(err)
    • If the error status code is greater than or equal to 500 directlynext(err)
    • Otherwise, the value is determined based on the configuration parametersnext(err)ornext
  • stream.pipe(res)Returns the stream data for the response

Finally see createRedirectDirectoryListener redirect logic:

/** * Create a directory listener that performs a redirect. That is, the redirectory implementation * in custom Send@private* /

function createRedirectDirectoryListener () {
  return function redirect (res) {
    /** * Call the hasTrailingSlash method inside the send library to determine if the path ends in '/'. * 404 */ when no resource is matched
    if (this.hasTrailingSlash()) {
      this.error(404)
      return
    }

    // Redirection logic, redirection to path/, is basically the same as the send library implementation
    // get original URL
    var originalUrl = parseUrl.original(this.req)

    // append trailing slash
    originalUrl.path = null
    originalUrl.pathname = collapseLeadingSlashes(originalUrl.pathname + '/')

    // reformat the URL
    var loc = encodeUrl(url.format(originalUrl))
    var doc = createHtmlDocument(
      'Redirecting'.'Redirecting to <a href="' + escapeHtml(loc) + '" >' + escapeHtml(loc) + '</a>'
    )

    // Set the redirection status code
    res.statusCode = 301
    // Set the relevant headers for redirection
    res.setHeader('Content-Type'.'text/html; charset=UTF-8')
    res.setHeader('Content-Length', Buffer.byteLength(doc))
    res.setHeader('Content-Security-Policy'."default-src 'none'")
    res.setHeader('X-Content-Type-Options'.'nosniff')
    // Redirect to loC's address
    res.setHeader('Location', loc)
    res.end(doc)
  }
}
Copy the code

The core logic of this redirection is to get the address path/ to redirect, and then set the response header to redirect:

// Set the redirection status code
res.statusCode = 301
// Set the relevant headers for redirection
res.setHeader('Content-Type'.'text/html; charset=UTF-8')
res.setHeader('Content-Length', Buffer.byteLength(doc))
res.setHeader('Content-Security-Policy'."default-src 'none'")
res.setHeader('X-Content-Type-Options'.'nosniff')
// Redirect to loC's address
res.setHeader('Location', loc)
res.end(doc)
Copy the code

conclusion

If you like this article ❤️❤️ 👍👍 👍👍 tweet ❤️❤️❤️ ha ~ ~ ~

I also recommend you read my other Nuggets articles:

  • Explain the implementation principle of NodeJs static file hosting service in “Send” source code
  • Explain the Node middleware model architecture in the source code of Connect
  • NodeJS techniques in live-Server source code
  • Ts Master: 22 examples to delve into Ts’s most obscure advanced type tools 👍 1.5K
  • Here are 28 Canvas libraries that will make you scream “wow!” 👍 852
  • In-depth explanation of VsCode advanced debugging and use skills in various scenarios
  • Vue + TS +class+ annotation style development pit full guide

I am leng hammer, I and the front end of the story continues……