** It is very easy to start an Express service for retrieving WASM files **

The source code for Express and the current mainstream libraries are all written in TypeScript, calling for a full switch to TypeScript

Because this article is an improvised piece of service code from my own project, TypeScript is not used here

** Note: ** Whether javaScript or Node.js framework source code is actually not difficult, a little thought can see very thoroughly, this article is just in the use of WASM, may not be as professional as others analysis

As we all know, when Express was introduced, it had to be called to get the app object, so we can see that Express was introduced as a function, which is viewed in the source code

** First analyze the @types package about TypeScirpt source **

To analysis the javaScript

Express originally introduces a function, but it has some express.static methods. So let’s go to Core. Express and look at its interface

The interface that the initial introduced function follows inherits from The Application

The interface format for request and response should be relatively simple, as will be written below

The Application interface inherits EventEmitter IRouter Express.Application

As those of you who have studied TypeScript know, interfaces can inherit multiple interfaces at once, but classes can only inherit one at a time through extends

There are some important API definitions found:

From here, we can know the parameters of these important apis, etc.,

Let’s formally parse the javaScript source code for Express


Having seen the source code for @types, let’s take a look at some of the javaScript source code and make it a breeze

Source code entry:

It is true that the source entry exposes a function, consistent with the source code in @types

What does the createApplication function do

{ configurable: true, enumerable: true, writable: true, value: app }
Copy the code

This code is the property descriptor, vue 2.x get and set and access descriptor

The most important initialization, app.init(), is a local variable, there is no init method. There is a call mixin above, listen to the function name to know is mixed, do not understand to search, five minutes package will

Enter proto:

When you find initialization, you’re mounting four properties in your app, and the initial values are all empty objects

It turns out that the implementation of App.Listen also relies on HTTP modules, much like KOA

Static resource server implementation module

The serve-static library is used to implement the serve-static library, and I also use native Node.js to write static resource server, feel the entry level node.js can play ~

** Default exposure is a function ~ ** when entering serve-static

module.exports = serveStatic
function serveStatic (root, options) {
    return serveStatic(req,res,next) {
    ...
     if (path === '/'&& originalUrl.pathname.substr(-1) ! = ='/') {
      path = ' '
    }
    var stream = send(req, path, opts)
    stream.on('directory', onDirectory)
    if (setHeaders) {
      stream.on('headers'.setHeaders)
    }
    if (fallthrough) {
      stream.on('file'.function onFile () {
        forwardError = true
      })
    }
    stream.on('error'.function error (err) {
      if(forwardError || ! (err.statusCode < 500)) { next(err)return
      }
      next()
    })
    // pipe
    stream.pipe(res)
    }
}
Copy the code

Express-static returns a function that accepts a request and returns a response

This is a lot of code, but the core is the same as I was when I returned the WASM binary, sending () to return a readable stream, and then calling PIPE to import it into the RES and return it to the client, except that the PIPE method is self-defined on the prototype chain

The send method depends on the send library

The default export is displayed

 
function send (req, path, options) {
  return new SendStream(req, path, options)
}

function SendStream(){ Stream.call(this) .. / several codes}Copy the code

At first I thought the pipe call was a readable stream pipe, but I didn’t find SendStream returning a value. Pipei is a method that I defined on the prototype chain


SendStream.prototype.pipe = functionpipe (res) { //.. Var path = decode(this.path) // some code this.sendFile(path)}Copy the code

The core of the original return file is here:

It’s a little bit convoluted here, so it takes a little bit of patience

 fs.stat(path, function onstat (err, stat) {
  if (err && err.code === 'ENOENT'&&! extname(path) && path[path.length - 1] ! == sep) { // not found, check extensionsreturn next(err)
  }
  if (err) return self.onStatError(err)
  if (stat.isDirectory()) return self.redirect(path)
  self.emit('file', path, stat)
  self.send(path, stat)})Copy the code

Send: function send: function send: function send: function send: function send: function send

After finding this function, we finally call this.stream

It’s been three libraries, nearly 2000 lines of code, and still no response, but node.js has several native apis that can return a response, and it should be time to return a response

Go into this.stream and find the header and return the response

The prototype and this properties are attached to each module in accordance with the commonJS modular specification, which makes it difficult to read

At this point, static resource server source code and app.listen source code module source code parsing completed

Xiaobian static resource server, source code is easier to read ~

https://github.com/JinJieTan/util-static-server
Copy the code

App.get

The function first defines the get method when it has only one argument, and then the get method returns the app setting property, which is irrelevant to us.

This.lazyrouter () initializes the base Router object for the app instance and calls router.use to add two base layers to the router with callbacks to query and middleware.init. Let’s leave the process alone.

Router.route (path) var route = this._router.route(path) var route = this._router.route(path) The router is declared in the index.js file in the Router directory, and its stack property stores the various middle layers described by Layer. The route method is defined in the proto.route function with the following code:

As you can see, a new Route instance is created first; We then use the route.dispatch function as a callback to create a new layer instance, and after setting the route property of the layer to the route instance, push the layer onto the stack of the router(this of this. Stack is the router).

Figuratively speaking, this process is to create a new layer as the middle layer in the router stack array. The layer callback is route.dispatch.

Route [method].apply(route, slice.call(arguments, 1)); Make the generated route(not the router) call route.get. The key flows in route.get are as follows:

At this point, the program is finished loading the GET method. Let’s briefly review the process: First instantiate a Router object for your app. The stack property of this object is an array that holds the different middle layers of your app. An intermediate layer is represented by a Layer instance whose Handle property references the callback function. For the layer created by methods like GET, the handle is the route.dispatch function, and the custom callbacks in the GET method are stored in the route stack. If the routine continues to add additional routes to the app, the Router object will continue to generate a new layer to store the middleware and add it to its own stack.


App. use, add middleware source code:

It’s also called the first time to initialize a new Layer middle Layer


app.use = function use(fn) {
var offset = 0;
var path = '/';
var fns = flatten(slice.call(arguments, offset));
this.lazyrouter();
var router = this._router;

fns.forEach(function (fn) {
  router.use(path, function mounted_app(req, res, next) {
    var orig = req.app;
    fn.handle(req, res, function (err) {
      setPrototypeOf(req, orig.request)
      setPrototypeOf(res, orig.response)
      next(err);
    });
  });
  fn.emit('mount', this);
}, this);

return this;
};
Copy the code

Lazyrouter, which generates a new ****Layer each time it is initialized

app.lazyrouter = function lazyrouter() {
  if(! this._router) { this._router = new Router({caseSensitive: this.enabled('case sensitive routing'),
      strict: this.enabled('strict routing')}); this._router.use(query(this.get('query parser fn'))); this._router.use(middleware.init(this)); }};Copy the code

Instead of saving a lot of fault tolerance, there is a function called flatten, which flatters arrays

Rely on a separate third-party library, the code is very simple


function flattenForever (array, result) {
  for (var i = 0; i < array.length; i++) {
    var value = array[i]

    if (Array.isArray(value)) {
      flattenForever(value, result)
    } else {
      result.push(value)
    }
  }

  return result
}
Copy the code

This is also very clever,forEach passed this to the function, I didn’t know forEach could pass two values,

Then pass in the corresponding callback function


app.handle = function handle(req, res, callback) {
  var router = this._router;

  // final handler
  var done = callback || finalhandler(req, res, {
    env: this.get('env'),
    onerror: logerror.bind(this)
  });
  // no routes
  if(! router) { debug('no routes defined on app');
    done(a);return;
  }

  router.handle(req, res, done); };Copy the code

Check whether the first layer matches the path of the request. Query and middleware.init are the router initialization functions that execute trim_prefix(Layer, layerError, layerPath, path). And call layer.handle_request(req,res, next); Next is the closure next in router.handle. After these two layers are executed, the callback to the next function continues.

while(match ! = =true&& idx < stack.length) { layer = stack[idx++]; match = matchLayer(layer, path); route = layer.route; / /... Trim_prefix (Layer, layerError, layerPath, path);function trim_prefix(layer, layerError, layerPath, path) {
    if(layerPath.length ! == 0) { // Validate path breaks on a path separator var c = path[layerPath.length]if(c && c ! = ='/'&& c ! = ='. ') return next(layerError)

  // Trim off the part of the url that matches the route
  // middleware (.use stuff) needs to have the path stripped
  debug('trim prefix (%s) from url %s', layerPath, req.url);
  removed = layerPath;
  req.url = protohost + req.url.substr(protohost.length + removed.length);

  // Ensure leading slash
  if(! protohost && req.url[0] ! = ='/') {
    req.url = '/' + req.url;
    slashAdded = true;
  }

  // Setup base URL (no trailing slash)
  req.baseUrl = parentUrl + (removed[removed.length - 1] === '/'
    ? removed.substring(0, removed.length - 1)
    : removed);
}

debug('%s %s : %s', layer.name, layerPath, req.originalUrl);

if (layerError) {
  layer.handle_error(layerError, req, res, next);
} else{ layer.handle_request(req, res, next); }}}Copy the code

If the regexp attribute (determined from the route generated at load time) is ‘/’, then ‘/a’ does not match.

If the paths do not match, the while loop skips the while loop and matches the next layer of router.stack. If path matches regexp of the route, layer.handle_request(req, res, next) is executed; .

Layer. Handle_request function:

 Layer.prototype.handle_request = function handle(req, res, next) {
  var fn = this.handle;

  if (fn.length > 3) {
    // not a standard request handler
    returnnext(); } try { fn(req, res, next); } catch (err) { next(err); }};Copy the code

This is the tricky part, and the most convoluted, because we know that calling red.end will return a response-end match, otherwise Express would do a route-by-route match, and finalHandler (the final processing) will be called to return a response after determining that all matching requests have been executed

Finalhandler is another separate third-party library dedicated to handling responses

Core functions:


if (isFinished(req)) {
  write()
  return
}

  function write () {
  // response body
  var body = createHtmlDocument(message)

  // response status
  res.statusCode = status
  res.statusMessage = statuses[status]

  // response headers
  setHeaders(res, headers)

  // security headers
  res.setHeader('Content-Security-Policy'."default-src 'none'")
  res.setHeader('X-Content-Type-Options'.'nosniff')

  // standard headers
  res.setHeader('Content-Type'.'text/html; charset=utf-8')
  res.setHeader('Content-Length', Buffer.byteLength(body, 'utf8'))

  if (req.method === 'HEAD') {
    res.end()
    return
  }
  res.end(body, 'utf8')}Copy the code

It can be judged by the following functions:


function isFinished(msg) {
  var socket = msg.socket
  if (typeof msg.finished === 'boolean') {
    // OutgoingMessage
    returnBoolean(msg.finished || (socket && ! socket.writable)) }if (typeof msg.complete === 'boolean') {
    // IncomingMessage
    returnBoolean(msg.upgrade || ! socket || ! socket.readable || (msg.complete && ! msg.readable)) } // don't know return undefined }Copy the code

Check whether there are protocol upgrade events (such as websocket’s first handshake), whether there are socket objects, whether the socket is readable, and so on

The final call to createHtmlDocument assembles the data and returns a response ~

 function createHtmlDocument (message) {
  var body = escapeHtml(message)
    .replace(NEWLINE_REGEXP, '<br>')
    .replace(DOUBLE_SPACE_REGEXP, '   ')

  return '
      \n' +
    '<html lang="en">\n' +
    '<head>\n' +
    '<meta charset="utf-8">\n' +
    '<title>Error</title>\n' +
    '</head>\n' +
    '<body>\n' +
    '<pre>' + body + '</pre>\n' +
    '</body>\n' +
    '</html>\n'
}

Copy the code

So far, cost 4000 words to parse the express core all API, feel a little around, here especially get routing trigger, is the core of the entire source code.

Koa is more like a toy. The source code is very lightweight. You can look at koA first, then Express, then Node.js core module source code


Feel good, can order. – Read, support xiaobian

Concern public number: front-end peak, reply to add group can join the large front-end communication group