持续创作,加速成长!这是我参与「AdminJS · 6 月更文挑战」的第22天,点击查看活动详情

nodejs web 服务器

const http = require('http')

const hostname = '127.0.0.1'
const port = 3000

function callback(req, res) {
  res.statusCode = 200
  res.setHeader('Content-Type', 'text/plain')
  res.end('Hello World\n')
}

const server = http.createServer(callback)

server.listen(port, hostname, () => {
  console.log(`Server running at http://${hostname}:${port}/`)
})

http 的 createServer() 方法创建新的 HTTP 服务器并返回。每当接收到新请求时,都会调用 request 事件,即执行callback 回调,其提供两个对象:请求(http.IncomingMessage 对象)和响应(http.ServerResponse 对象)。

Node.js 是一个底层平台,为了让开发者的工作变得轻松有趣,社区在 Node.js 上构建了数千个库,比如: express。

express 提供了最简单而强大的方式来创建 Web 服务器,它的极简主义方法、没有偏见、专注于服务器的核心功能,是其成功的关键。

express 的使用

import express from 'express'

const app = express()

app.get('/', (req, res, next) => {
  res.end('hello world')
})

app.listen('7001', () => {
  console.log('listen at 7001')
})

下面我们从源码的角度简要的梳理下express的执行过程。

express 源码简要解析

var bodyParser = require('body-parser')
var EventEmitter = require('events').EventEmitter;
var mixin = require('merge-descriptors');
var proto = require('./application');
var Route = require('./router/route');
var Router = require('./router');
var req = require('./request');
var res = require('./response');

/**
 * Expose `createApplication()`.
 */
// 暴露出一个createApplication方法
exports = module.exports = createApplication;

/**
 * Create an express application. 创建一个express的应用
 */
// 返回一个app,这个app是一个函数,同时在函数下面挂载了很多属性
function createApplication() {
  var app = function(req, res, next) {
    app.handle(req, res, next);
  };
  // 往app上挂载属性  
  mixin(app, EventEmitter.prototype, false);
  mixin(app, proto, false);

  // 以req为原型生成request对象,同时在request挂载app
  app.request = Object.create(req, {
    app: { configurable: true, enumerable: true, writable: true, value: app }
  })

  // 以res为原型生成response对象,同时在response挂载app
  app.response = Object.create(res, {
    app: { configurable: true, enumerable: true, writable: true, value: app }
  })

  app.init();
  // 返回app
  return app;
}

/**
 * Expose the prototypes. 
 */
// 因为createApplication = exports,所以下面的代码就是在createApplication上挂载一些属性
exports.application = proto;
exports.request = req;
exports.response = res;

/**
 * Expose constructors. 往createApplication挂载 路由的构造函数 Route 和 Router
 */

exports.Route = Route;
exports.Router = Router;

/**
 * Expose middleware 在createApplication挂载一些属性
 */

exports.json = bodyParser.json
exports.query = require('./middleware/query');
exports.raw = bodyParser.raw
exports.static = require('serve-static');
exports.text = bodyParser.text
exports.urlencoded = bodyParser.urlencoded

/**
 * Replace removed middleware with an appropriate error message.
 */
// 下面的这些中间件不在绑定到express上,如果需要则需单独安装,express保持基本的功能即可
var removedMiddlewares = [
  'bodyParser',
  'compress',
  'cookieSession',
  'session',
  'logger',
  'cookieParser',
  'favicon',
  'responseTime',
  'errorHandler',
  'timeout',
  'methodOverride',
  'vhost',
  'csrf',
  'directory',
  'limit',
  'multipart',
  'staticCache'
]

removedMiddlewares.forEach(function (name) {
  Object.defineProperty(exports, name, {
    get: function () {
      throw new Error('Most middleware (like ' + name + ') is no longer bundled with Express and must be installed separately. Please see https://github.com/senchalabs/connect#middleware.');
    },
    configurable: true
  });
});

下面主要分析核心代码:

function createApplication() {
  var app = function(req, res, next) {
    app.handle(req, res, next);
  };
  // 往app上挂载属性  
  mixin(app, EventEmitter.prototype, false);
  mixin(app, proto, false);

  // 以req为原型生成request对象,同时在request挂载app
  app.request = Object.create(req, {
    app: { configurable: true, enumerable: true, writable: true, value: app }
  })

  // 以res为原型生成response对象,同时在response挂载app
  app.response = Object.create(res, {
    app: { configurable: true, enumerable: true, writable: true, value: app }
  })

  app.init();
  // 返回app
  return app;
}

var app = express()

var app = express()执行后返回一个app,这个app其实是一个函数:

var app = function(req, res, next) {
    app.handle(req, res, next);
};

在这个函数app下挂载了很多属性:

var proto = require('./application');

mixin(app, EventEmitter.prototype, false);
mixin(app, proto, false);

app到底是什么?

application.js代码如下,就是往app上挂载很多属性:

var app = exports = module.exports = {};

app.use = () => {}
app.route = () => {}
// 在app上把get, post等方法挂载上去
methods.forEach(function(method){
  app[method] = function(path){
    if (method === 'get' && arguments.length === 1) {
      // app.get(setting)
      return this.set(path);
    }
    // 在this上生成一个Router的实例
    this.lazyrouter();

    var route = this._router.route(path);
    route[method].apply(route, slice.call(arguments, 1));
    return this;
  };
});

app.listen = function listen() {
  var server = http.createServer(this);
  return server.listen.apply(server, arguments);
};

每当请求过来的时候,就会执行http.createServer里面的回调函数,即app(app是一个函数)。所以,app到底什么呢?app就是一个回调函数,每当发起请求的时候就会执行app,然后把req, res传入,然后执行:

var app = function(req, res, next) {
    app.handle(req, res, next);
};

app.handle = function handle(req, res, callback) {
  var router = this._router;
  ......  
  // 在这里就来处理路由的逻辑,并返回给前端
  router.handle(req, res, done);
};

项目中我们写的路由,都会被收集起来,然后根据req的method和path来匹配,匹配后就执行对应的逻辑。

app.get('/', (req, res, next) => {
  res.end('hello world')
})

var express = require('express')
var router = express.Router()
router.get('/list', function (req, res, next) {})

express-generator脚手架

生成express项目

npm install express-generator -g
express express-test
npm install && npm start

用脚手架生成的项目是一个全栈的项目,包括服务端和前端页面,目前流行的开发模式是前后端分离,前端开发前端的,后端开发后端的。所以我们后端只要求开发接口,就可以忽略这两个文件。

通过--no-view可以创建不带前端页面的项目:

express express-test --no-view

app.js 详解

// 如果找不到页面,就返回404
var createError = require('http-errors')
var express = require('express')
var path = require('path')
var fs = require('fs')
// 解析cookie
var cookieParser = require('cookie-parser')
// 日志模块
var logger = require('morgan')
// 路由
const blogRouter = require('./routes/blog')
const userRouter = require('./routes/user')

// app就是上面解析的回调函数,但是每次不同的请求,里面的req,res不同
var app = express()

app.use(logger('dev'))

// 下面两行是对post方法传递的数据的处理,处理后的数据放在req.body上
// express.json()只处理json格式的post
app.use(express.json())
// 这个处理除了json格式的其他post数据,比如表单数据,表单数据格式就是x-www-form-urlencoded
app.use(express.urlencoded({ extended: false }))
app.use(cookieParser())
// 返回静态文件,我们这里是api开发,不涉及到前端
// app.use(express.static(path.join(__dirname, 'public')));

// 注册路由 这里的路径和blogRouter里面的路径进行合并,也就是这里的路径是一个父路径
app.use('/api/blog', blogRouter)
app.use('/api/user', userRouter)

// 下面是用来捕获错误的代码
// catch 404 and forward to error handler
app.use(function (req, res, next) {
  next(createError(404))
})

// error handler
app.use(function (err, req, res, next) {
  // set locals, only providing error in development
  res.locals.message = err.message
  res.locals.error = req.app.get('env') === 'dev' ? err : {}

  // render the error page
  res.status(err.status || 500)
  res.render('error')
})

module.exports = app

启动文件 bin/www

// 引用app.js导出的app
var app = require('../app');
var http = require('http');

var server = http.createServer(app);
server.listen(port);

package.json

"scripts": {
  "start": "node ./bin/www",
  "dev": "cross-env NODE_ENV=dev nodemon ./bin/www",
  "prd": "cross-env NODE_ENV=production nodemon ./bin/www"
},

声明:本文仅供个人学习使用,来源于互联网,本文有改动,本文遵循[BY-NC-SA]协议, 如有侵犯您的权益,请联系本站,本站将在第一时间删除。谢谢你

原文地址:如何理解express框架