express4.0

express学习

本文档基于express4.x版本

http://expressjs.com/

express Hello World

  • mkdir hello && cd hello && npm install express
  • 新建app.js内容如下

    var express = require('express');
    var app = express();
    
    app.get('/', function(req, res){
      res.send('hello world');
    });
    
    app.listen(3000);
    
  • node app.js,然后浏览器访问http://localhost:8888可以看到hello world

express应用模板

可以使用express工具生成应用程序框架,

  • npm install express-generator -g
  • express -h显示选项
  • express myapp在当前目录创建名为myapp的express程序
  • cd myapp && npm install安装所需模块
  • set DEBUG=myapp & node ./bin/www运行app
  • 浏览器访问http://localhost:3000

路由基础

路由指确定程序如何针对特定请求进行反应,这个请求是一个URI和它的HTTP请求方法。

每一个路由有一个处理函数,当路由匹配的时候执行。

路由定义格式为:app.VERB(PATH, HANDLER),其中app是一个express实例,VERB是一个HTTP请求方法,PATH是服务器上的一个路径,HANDLER是用于处理请求的函数。

一下代码展示app的基本路由

// respond with "Hello World" on the homepage
app.get('/', function (req, res) {
    res.send('Hello World');
});

// accept POST request on the homepage
app.post('/', function (req, res) {
    res.send('Got a POST request');
});

// accept PUT request at /user
app.put('/user', function (req, res) {
    res.send('got a PUT request at /user');
});

// accept DELETE request at /user
app.delete('/user', function (req, res) {
    res.send('got a DELETE request at /user');
});

使用中间件

express程序实质上是一系列的中间件调用。

中间件是一个可以访问请求、相应对象和下一个中间件的函数,中间件功能有:

  • 执行任意代码
  • 修改请求和响应对象
  • 结束请求-响应周期
  • 调用栈上的下一个中间件

如果当前中间件不结束请求-响应周期,它必须调用next()将控制传递给下一个中间件,否则请求将会悬挂。

中间件有一个可选的mount路径,中间件可以再应用程序级别或者路由级别进行加载。同样,一系列的中间件函数可以同时加载,这就在mount点创建了一个中间件子栈。

express应用程序可以使用一下类型的中间件:

  • 应用程序级别
  • 路由级别
  • built-in中间件
  • 第三方中间件

应用程序级别中间件

应用程序级别中间件绑定在express示例上,使用app.use()或者app.VERB()

var app = express();

// a middleware with no mount path;
// gets executed for every request to the app
app.use(function (req, res, next) {
    console.log('Time: ', Date.now());
    next();
});

// a middleware mounted on /user/:id;
// will be executed for any type of HTTP request to /user/:id
app.use('/user/:id', function (req, res, next) {
    console.log('Request Type: ', req.method);
    next();
});

// a route and its handler function (middleware system)
// which handles GET requests to /user/:id
app.get('/user/:id', function (req, res, next) {
    res.send('user');
});

下面代码在mount点加载一系列中间件

// a middleware sub-stack which prints request info
// for any type of HTTP request to /user/:id
app.use('/user/:id', function (req, res, next) {
    console.log('request url: ', req.originUrl);
    next();
}, function (req, res, next) {
    console.log('request type: ', req.method);
    next();
});

路由处理程序,作为中间件系统的一部分,使得为同一个路径定义多个路由成为可能。下面的例子中,为GET /user/:id定义了两个路由。第二个路由没有任何语法问题,但是永远得不到调用,因为第一个路由终止了请求-响应循环。

// a middleware sub-stack which handles GET requests to /user/:id
app.get('/user/:id', function (req, res, next) {
    console.log('id: ', req.params.id);
    next();
}, function (req, res, next) {
    res.send('user info');
});


// handler for /user/:id which prints the user id
app.get('/user/:id', function (req, res, next) {
    res.end(req.params.id);
});

如果想要跳过路由中间件栈剩下的中间件,调用next('route')会将控制权转交给下一个路由。需要注意的是:next('route')值能在使用app.VERB()或者router.VERB()加载的中间件中使用。

// a middleware sub-stack which handles
// GET requests to /user/:id
app.get('/user/:id', function (req, res, next) {
    // if user id is 0, skip to the next route
    if (req.params.id === 0) {
        next('route');
    }
    // else pass the control to the next middleware in this stack
    else {
        next();
    }
}, function (req, res, next) {
    res.render('regular');
});


// handler for /user/:id which renders a special page
app.get('/user/:id', function (req, res, next) {
    res.render('special');
});

路由级别中间件

路由级别中间件与应用程序级别中间件工作一样,唯一的区别是他们bound toexpress.Router()的实例

var router = express.Router();

路由级别中间件通过router.use()或者router.VERB()加载。

前面应用程序级别的中间件可以使用路由级别代替:

var app = express();
var router = express.Router();

// a middleware with no mount path,
// get executed for every request to the router
router.use(function (req, res, next) {
    console.log('Time:', Date.now());
    next();
});

// a middleware sub-stack shows request
// info for any type of HTTP request to /user/:id
router.use('/user/:id', function (req, res, next) {
    console.log('request url:', req.originalUrl);
    next();
}, function (req, res, next) {
    console.log('request type:', req.methos);
    next();
});

// a middle ware sub-stack which handles GET requests to /user/:id
router.get('/user/:id', function (req, res, next) {
    // if user id is 0, skip to the next router
    if (req.params.id === 0) {
        next('route');
    }
    // else pass the control to the next middleware in this stack
    else {
        next();
    }
}, function (req, res, next) {
    res.render('regular');
});

// handler for /user/:id which renders a special page
router.get('/user/:id', function (req, res, next) {
    console.log(req.params.id);
    res.render('special');
});

// mount the router on the app
app.use('/', router);

自带中间件

express4.X之后不再依赖于Connect。除了自带express.static之外,所有其他中间件都放到单独模块中。查看中间件列表

express.static(root, [options])

express.static基于serve-static模块,负责为express应用程序提供静态资源服务。

root参数为静态资源存放的根目录。

可选的options对象可以有以下属性:

  • dotfiles控制点文件服务,可选值为allow,deny,’ignore’默认为ignore
  • etag控制etag生成,默认为true
  • extensions设置文件后缀名补充,默认为false
  • index设置目录访问的返回,默认为index.html,设置为false可以禁止目录访问
  • lastModified根据文件修改时间设置Last-Modified报头,默认true
  • maxAge设置Cache-Control报头的缓存控制时间,单位为毫秒,默认为0
  • redirect当路径名是目录时,重定向到包含结尾/的目录,默认true
  • setHeaders函数用于为文件设置HTTP头

以下是例子:

var options = {
    dotfiles: 'ignore',
    etag: false,
    extensions: ['htm', 'html'],
    index: false,
    maxAge: '1d',
    redirect: false,
    setHeaders: function (req, res, stat) {
        res.set('x-timestamp', Date.now());
    }
};

app.use(express.static('public', options));

一个应用程序可以有多个静态目录:

app.use(express.static('public'));
app.use(express.static('uploads'));
app.use(express.static('files'));

访问serve-static查看详细选项

第三方中间件

express是一个路由和中间件web框架,自身包含很少功能,可以通过第三方中间件添加所需功能。

安装所需模块并且在应用程序级别或者路由级别加载。

下面的例子使用cookie-parser来解析cookie

npm install cookie-parser
var express = require('express');
var app = express();
var cookieParser = require('cookie-parser');

app.use(cookieParser());

查看express中间件列表

错误处理

异常处理中间件与其他中间件相比的区别是函数参数为四个(err, req, res, next)

app.use(function (err, req, res, next) {
    console.error(err.stack);
    res.status(500).send('Something broke!');
});

尽管不是强制的,通常来说错误处理中间件定义在最后:

var bodyParser = require('body-parser');
var methodOverride = require('method-override');

app.use(bodyParser());
app.use(methodOverride());

app.use(function (err, req, res, next) {
    // logic
});

错误处理中间件返回的内容可以使任意的,比如HTML错误页面,简单的消息,JSON等等。

为了方便组织,可能需要定义多个错误处理中间件,比如你可能想为XHR请求定义错误处理程序。

var bodyParser = require('body-parser');
var methodOverride = require('method-override');

app.use(bodyParser());
app.use(methodOverride());
app.use(logErros);
app.use(clientErrorHandler);
app.use(errorHandler);

其中logErrors将请求和错误信息输出到stderr

function logErrors(err, req, res, next) {
    console.error(err.stack);
    next(err);
}

clientErrorHandler定义如下:

function clientErrorHandler(err, req, res, next) {
    if (req.xhr) {
        res.status(500)
            .send({
                error: 'Something blew up'
            });
    }
    else {
        next(err);
    }
}

最后的errorHandler捕获所有错误

function errorHandler(err, req, res, next) {
    res.status(500);
    res.render('error', {
        error: err
    });
}

调试express应用程序

express使用debug模块来log路由匹配,中间件使用,应用粗模式等。

debug类似于console.log但是不同的是发布版本时不需要注释debug日志语句,通过环境变量DEBUG调节日志输出

查看express内部日志的方法:启动app时设置DEBUG环境变量为express:*

DEBUG=express:* node index.js

如果只想输出路由信息,设置DEBUG=express:router,如果只想查看应用程序相关log,设置DEBUG=express:application

更多debug信息,查看debug guide

使用模板引擎

在express渲染模板文件之前,必须执行以下设置

  • views,模板文件存放目录,如app.set('views', './views')
  • view engine,使用的模板引擎,如app.set('view engine', 'jade')

然后安装对应模板引擎的package

npm install jade --save

兼容于express的模板引擎会暴露一个__express(filePath, options, callback)的接口,用于提供给res.render()调用来渲染模板

一些模板引擎不遵守这个约定consolidate.js可以为流行的node模板引擎创建转换,这样就可以与express无缝衔接。

模板引擎设置好之后,不必显式指定模板引擎或者加载模块,express会自动加载

app.engine('jade', require('jade').__express);

在视图目录下创建一个jade模板文件index.jade

html
    head
        title!= title
    body
        h1!= message

然后创建一个路由渲染index.jade

app.get('/', function (req, res) {
    res.render('index', {
        title: 'hey',
        message: 'Hello there!'
    });
});

此时访问主页即可看到欢迎界面,查看如何开发express模板引擎可以学习更多知识

express4.X API

Application

express()

创建express应用程序

var express =  require('express');
var app = express();

app.get('/', function (req, res) {
    res.send('hello world');
});

app.listen(3000);

settings

express应用程序选项可以通过app.set()进行设置,通过app.get()读取以下是支持的选项

  • trust proxy说明app位于front-facing proxy之后,X-Forwarded-*报头可以用来确定客户端连接和IP地址。需要注意的是X-Forwarded-*报头很容易造假,所以检测到的IP地址并不可靠。

    trust proxy默认是禁止的。当激活时,express尝试获取通过front-facing proxy连接的客户端IP地址。req.ips属性包含客户端连接中的IP地址。一下只可以用于设置:

    TypeValue
    Boolean如果是true,express假设客户端IP地址是X-Forwarded-*中最左的值。如果false,express认为直接连接客户端,req.connection.remoteAddress是客户端地址,默认值为false。
    IP 地址

    可信任的ip地址,子网或者IP地址数组。以下是预定义子网名字

    • loopback – 127.0.0.1/8,::1/128
    • linklocal – 169.254.0.0/16,fe80::/10
    • uniquelocal – 10.0.0.0/8, 172.16.0.0/12, 192.168.0.0/16, fc00::/7

    IP地址可以如下设置

    
    app.set('trust proxy', 'loopback'); // specify a single subnet
    app.set('trust proxy', 'loopback, 123.123.123.123'); // subnet and an address
    app.set('trust proxy', 'loopback, linklocal, uniquelocal');
    app.set('trust proxy', ['loopback', 'linklocal', 'uniquelocal']);
    

    When specified, the IP addresses or the subnets are excluded from the address determination process, and the untrusted IP address nearest to the application server is determined as the client’s IP address.

    NumberTrust the nth hop from the front-facing proxy server as the client.
    Function自定义信任实现。只有你知道自己在做什么的时候才这样做

    
    app.set('trust proxy', function (ip) {
        if (ip === '127.0.0.1' || ip === '123.123.123.123') {
            return true;    // trusted IPs
        }
        else {
            return false;
        }
    });
    
    

    查看proxy-addr更多信息。

  • env环境模式,默认为process.env.NODE_ENV(NODE_ENV环境变量)或者development

  • subdomain offsetThe number of dot-separated parts of the host to remove to access subdomain, two by default.
  • jsonp callback name修改json回调名字?callback=
  • json replacer JSON replacer callback, 默认为null
  • json spaces设置为JSON缩进,默认禁止
  • case sensitive routing enable区分大小写,默认不区分,即/Foo/foo一样
  • strict routing enable严格路由,默认情况下/foo/foo/认为相同
  • view cache设置模板编译缓存,发布情况下默认激活
  • view engine 引擎默认后缀名
  • views视图目录路径,默认为process.cwd() + '/views'
  • query parserquery解析器,simple或者extended,默认为extended,简单query解析器基于node的原生解析器querystring,扩展解析器基于qs
  • x-powered-by激活X-Powered-By: ExpressHTTP报头,默认激活
  • etag设置ETag响应头

    TypeValue
    Booleantrue允许强ETag。这是默认设置。false禁止ETag
    String如果是`strong`使用强ETag。如果是`weak`使用弱ETag
    Function自定义ETag实现。只有你知道在做什么才使用它

    
    app.set('etag', function (body, encoding) {
        return generateHash(body, encoding);
    });
    
    

    查看更多HTTP ETag报头

app.set(name, value)

为程序name设置value

app.set('title', 'My site');
app.get('title');   // My site

app.get(name)

获取设置的name值

app.get('title');   // undefined

app.set('title', 'My site');
app.get('title');   // My site

app.enable(name)

设置name属性为true

app.enable('trust proxy');
app.get('trust proxy');     // true

app.enabled(name)

检查name是否激活

app.enabled('trust proxy');     // false

app.enable('trust proxy');
app.enabled('trust proxy');     // true

app.disable(name)

设置name值为false

app.disable('trust proxy');
app.get('trust proxy');     // false

app.disabled(name)

检查name是否禁止

app.disabled('trust proxy');        // true

app.enable('trust proxy');
app.disabled('trust proxy');        // false

app.use([path], [function…], function)

在path路径Mount中间件函数。如果path没有设置,默认为/.

中间件mount到path之后当请求路径的起始部分匹配了path就会执行中间件。

由于path默认值为/所以默认情况下所有的请求都会执行中间件

// this middleware will be executed for every request to the app
app.use(function (req, res, next) {
    console.log('Time: %d', Date.now());
    next();
});

匹配的中间件函数会顺序执行,所以中间件配置顺序很重要

// this middleware will not allow the request to go beyond it
app.use(function (req, res, nex) {
    res.send('hello world');
});

// request will never reach this route
app.get('/', function (req, res) {
    res.send('Welcome');
});

path可以使一个字符串,一个模式,一种正则表达式或者包含三种模式描述路径的数组。

TypeExample
Path
    
        // will match paths starting with /abcd
        app.use('/abcd', function (req, res, next) {
            next();
        });
    
    
Path Pattern

        // will match paths starting with /abcd and /abd
        app.use('/abc?d', function (req, res, next) {
            next();
        });

        // will match paths starting with /abcd, /abbcd, /abbbbcd and so on
        app.use('/ab+cd', function (req, res, next) {
            next();
        });

        // will match paths starting with /abcd, abxcd, /abfoocd and so on
        app.use('/ab*cd', function (req, res, next) {
            next();
        });

        // will match paths starting with /ad and abcd
        app.use('/a(bc)?d', function (req, res, next) {
            next();
        });
    
Regular Expression
    
    // will match paths starting with /abc and /xyz
    app.use(/\/abc|\/xyz/, function (req, res, next) {
        next();
    });
    
    
Array
    
    // will match paths starting with /abcd, /xyza, /lmn, and /pqr
    app.use(['/abcd', '/xyza', /\/lmn|\/pqr/], function (req, res, next) {
        next();
    });
    
    

function可以使一个中间件函数,或者一系列中间件函数,或者包含中间件函数的数组或者他们的组合。由于路由和程序都实现了中间件接口,可以再其他地方像使用中间件那样使用。

UsageExample
Single Middleware

中间件函数可以定义,并且在本地mount

    
        app.use(function (req, res, next) {
            next();
        });
    
    

路由也是合法的中间件

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

        app.use(router);
    
    

express应用程序也是合法中间件

    
        var subApp = express();
        subApp.get('/', function (req, res, next) {
            next();
        });
        app.use(subApp);
    
    
Series of Middleware

可以再一个mount路径使用多个中间件

    
        var r1 = express.Router();
        r1.get('/', function (req, res, next) {
            next();
        });

        var r2 = express.Router();
        r2.get('/', function (req, res, next) {
            next();
        });

        app.use(r1, r2);
    
    
Array

将多个中间件组装到数组中,如果数组形式的中间件是第一个参数或者唯一参数,需要制定mount路径

    
        var r1 = express.Router();
        r1.get('/', function (req, res, next) {
            next();
        });

        var r2 = express.Router();
        r2.get('/', function (req, res, next) {
            next();
        });

        app.use('/', [r1, r2]);
    
    
combination
    
        function mw1(req, res, next) {
            next();
        }
        function mw2(req, res, next) {
            next();
        }

        var r1 = express.Router();
        r1.get('/', function (req, res, next) {
            next();
        });

        var r2 = express.Router();
        r2.get('/', function (req, res, next) {
            next();
        });

        var subApp = express();
        subApp.get('/', function (req, res, next) {
            next();
        });

        app.use(mw1, [mw2, r1, r2], subApp);
    
    

以下是一些使用express.static中间件的例子。

public目录提供静态文件服务

// GET /styles.css etc
app.use(express.static(__dirname + '/public'));

将中间件mount到/static路径,这样只有请求路径以/static开头才响应。

// GET /static/style.css etc.
app.use('/static', express.static(__dirname + '/public'));

禁止静态文静请求log:将log中间件配置到静态文件中间件之后

app.use(express.static(__dirname + '/public'));
app.use(logger());

在多个不同目录提供静态文件服务,放在最前面的具有最高优先级

app.use(express.static(__dirname + '/public'));
app.use(express.static(__dirname + '/files'));
app.use(express.static(__dirname + '/uploads'));

app.engine(ext, callback)

为指定后缀名模板指定模板引擎回调。

默认情况,express使用require()根据文件扩展名加载模板引擎。例如,如果渲染foo.jade,express会内部执行以下操作,并且将回调进行缓存以提高性能。

app.engine('jade', require('jade').__express);

对于没有提供.__express接口的引擎,需要定义映射,比如下面的例子

app.engine('html', require('ejs').renderFile);

在上面的例子中EJS提供.renderFile()方法实现express所需要的(path, options, callback)函数签名

app.param([name], callback)

映射逻辑到路由参数。例如当:user在路由路径中出现的时候,可以通过为路由提供req.user映射用户逻辑,或者针对参数进行验证。

注意:

  • param回调函数本地化到定义的路由。不会被mount的app或者路由继承。因此app上定义的param回调只有当路由参数在app路由上定义的时候才触发
  • param回调在请求-响应周期中只会调用一次,即使这个参数匹配多个路由
app.param('id', function (req, res, next, id) {
    console.log('Called only once');
    next();
});

app.get('/user/:id', function (req, res, next) {
    console.log('although this matches');
    next();
});

app.get('/user/:id', function (req, res) {
    console.log('and this matches too');
    res.end();
});

一下代码展示callback的用法,这很大程度上类似于中间件。代码根据参数尝试获取用户信息,如果成功,赋值给req.user,失败调用next(err)

app.param('user', function (req, res, next, id) {
    User.find(id, function (err, user) {
        if (err) {
            next(err);
        }
        else if (user) {
            req.user = user;
            next();
        }
        else {
            next(new Error('failed to load user'));
        }
    });
});

app.METHOD(path, [callback…], callback)

app.METHOD()方法为express程序提供路由功能,其中METHOD为HTTP方法或增强方法,使用小写,例如app.get(),app.post(),app.patch()

express支持的方法有:

  • get
  • post
  • put
  • head
  • delete
  • options
  • trace
  • copy
  • lock
  • mkcol
  • move
  • purge
  • propfind
  • proppatch
  • unlock
  • report
  • mkactivity
  • checkout
  • merge
  • m-search
  • notify
  • subscribe
  • unsubscribe
  • patch
  • search
  • connect

对于不能转化为合法JavaScript变量的方法,使用中括号访问法如:

app['m-search']('/', function () {});

可以设置多个回调,他们都相同,就像中间件一样工作。唯一的不同时这些回调可以通过调用next('route')跳过剩余的路由回调函数。这种方法可以用于执行条件路由,然后将控制传递给后续路由。

下面的代码展示最简单的路由定义。express将路径字符串转换为正则表达式,用于处理可能的请求。查询字符串不参与匹配。例如GET /可以匹配GET /?name=tobi

app.get('/', function (req, res) {
    res.send('hello world');
});

可以使用正则表达式对路径执行更多限制。例如以下代码可以匹配GET /commits/71dbb9c,也可以匹配GET /commits/71dbb9c..4c084f9

app.get(/^\/commits\/(\w+)(?:\.\.(\w+))?$/, function(req, res){
  var from = req.params[0];
  var to = req.params[1] || 'HEAD';
  res.send('commit range ' + from + '..' + to);
});

可以传递多个回调。有助于中间件复用

app.get('/user/:id', user.load, function () {
    // ...
});

如果有多个通用中间件,可以使用route API中的all

var middleware = [loadForum, loadThread];

app.route('/forum/:fid/thread/:tid')
    .all(loadForum)
    .all(loadThread)
    .get(function () {})
    .post(function () {});

app.all(path, [callback..], callback)

这个方法类似于app.METHOD()不同的是,它可以匹配所有HTTP方法。

这个方法对于执行全局逻辑或者针对满足特定前缀的路径非常有用。例如如果你将以下路由放在所有路由定义之前,这就会强制所有路径访问都进行验证,并且自动加载用户。需要注意的是这种回调不应该终止请求,loadUser执行操作之后调用next()传递控制权给下一个中间件。

app.all('*', requireAuthentication, loadUser);

与下面等价

app.all('*', requireAuthentication);
app.all('*', loadUser);

另一种用法是对特定路径执行指定任务如:

app.all('/api/*', requireAuthentication);

app.route(path)

返回一个路由实例,可以用户处理HTTP请求。使用app.route()是避免多次重复路径的推荐方法

var app = express();

app.route('/events')
    .all(function(req, res, next) {
        // runs for all HTTP verbs first
        // think of it as route specific middleware
    })
    .get(function (req, res, next) {
        // res.json(...);
    }).post(function (req, res, next) {
        // may be add a new event..
    });

app.locals

用于设置提供给程序所有模板的变量。对于为模板提供辅助函数和app-level数据非常有用

app.locals.title = 'My App';
app.locals.strftime = require('strftime');
app.locals.email = 'me@myapp.com';

app.locals是一个JavaScript对象。其属性会被设置为app局部变量

app.locals.title
// => 'My App'

app.locals.email
// => 'me@myapp.com'

默认情况下。express值暴露一个app-level级别变量:settings

app.set('title', 'My App');
// use settings.title in a view

app.render(view, [options], callback)

使用回调函数渲染view,这是app-level的res.render()

app.render('email', function (err, html) {
    // ...
});

app.render('email', {name: 'Tibi'}, function (err, html) {
    // ...
});

app.listen()

在指定主机,端口绑定并监听。这个方法与node的http.Server#listen()相同

var express = require('express');
var app = express();
app.listen(3000);

express()返回的app是一个JavaScript Function,可以传递给node的HTTP服务器作为回调来处理请求。这样就支持同时提供同一份代码支持HTTP和HTTPS版本的app。

var express = require('express');
var https= require('https');
var http = require('http');
var app = express();

http.createServer(app).listen(80);
https.createServer(options, app).listen(443);

The app.listen() method is a convenience method for the following (if you wish to use HTTPS or provide both, use the technique above):

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

app.path()

返回app路径

var app = express();
var blog = express();
var blogAdmin = express();

app.use('/blog', blog);
blog.use('/admin', blogAdmin);

console.log(app.path());        // ''
console.log(blog.path());       // '/blog'
console.log(blogAdmin.path());      // '/blog/admin'

当app具有复杂的mount时。这个方法将变得复杂,一次推荐使用req.baseUrl来获取路径

app.mountpath

返回子app mount的路径或者模式

var admin = express();

admin.get('/', function (req, res) {
    console.log(admin.mountpath);       // /admin
    res.send('Admin Homepage');
});

app.use('/admin', admin);       // mount the sub app

这个属性与req.baseUrl类似,不同的是app.mountpath返回模式。

如果子app在多个路径mustache上进行了mount。app.mountpath返回所有模式的列表

var admin = express();
admin.get('/', function (req, res) {
    console.log(admin.mountpath);       // ['/adm*n', '/manager']
    res.send('Admin Homepage');
});

var secret = express();
secret.get('/', function (req, res) {
    console.log(secret.mountpath);      // /secr*t
    res.send('Admin Secret');
});

admin.use('/secr*t', secret);
app.use(['/adm*n', '/manager'], admin);

app.on(‘mount’, callback(parent))

在子app上监听mount事件,当它被父app mount的时候触发。父app作为参数传递给回调函数。

var admin = express();

admin.on('mount', function (parent) {
    console.log('Admin Mounted');
    console.log(parent);        // refers to the parent app
});

admin.get('/', function (req, res) {
    res.send('Admin Homepage');
});

app.use('/admin', admin);

Request

req.params

这个属性是包含路由参数映射的对象,例如如果使用/user/:name进行路由,req.params.name上就设置了name属性。req.params默认值为{}

// GET /user/tj
req.params.name
// => 'tj'

当路由定义使用正则表达式时,捕获分组将出现在req.params[N]数组对应元素,其中N是底n个捕获组。这个规则可以使用在未知的匹配情况下,如/file/*

// GET /file/js/jquery.js
req.params[0];
// => 'js/jquery.js'

req.query

保存query字符串解析后的数据,默认为{}

// GET /search?q=tobi+ferret
req.query.q;
// => 'tobi ferret'

// GET /shoes?order=desc&shoe[color]=blue^shoe[type]=converse
req.query.order
// => 'desc'

req.query.shoe.color
// => 'blue'

req.query.shoe.type
// => 'converse'

req.body

包含请求体解析后数据的键值对。默认为undefined,通过使用解析中间件渲染后得到对应值,常见中间件为body-parsermulter

下面的例子展示使用body-parser中间件来渲染req.body

var app = require('express')();
var bodyParser = require('body-parser');
var multer = require('multer');

app.use(bodyParser.json());     // for parsing application/json
app.use(bodyParser.urlencoded({extended: true}));       // for parsing application/x-www-from-urlencoded
app.use(multer());      // for parsing multipart/form-data

app.post('/', function (req, res) {
    console.log(req.body);
    res.json(req.body);
});

req.param(name, [defaultValue])

返回name对应的参数值。

// ?name=tobi
req.param('name');
// => 'tobi'

// POST name=tobi
req.param('name');
// => 'tobi'

// user/tobi for /user/:name
req.param('name');
// => 'tobi'

查询顺序如下:

  • req.params
  • req.body
  • req.query

可以指定defaultValue,当参数名没有查找到的时候的默认值。

通常来说,最好的方法是直接查询对应对象寻找值。

req.route

返回当前匹配的Route

app.get('/user/:id?', function userIdHandler(req, res) {
    console.log(req.route);
    res.send('GET');
});

输出结果类似:

{ path: '/user/:id?',
  stack:
   [ { handle: [Function: userIdHandler],
       name: 'userIdHandler',
       params: undefined,
       path: undefined,
       keys: [],
       regexp: /^\/?$/i,
       method: 'get' } ],
  methods: { get: true } }

req.cookies

需要cookieParser()中间件来解析,生成用户代理包含cookie的键值对,默认为{}

// cookie: name=tj
req.cookies.name
// => 'tj'

参考cookie-parser更多信息。

req.signedCookies

使用cookieParser(secret)渲染对象。 It contains signed cookies sent by the user-agent, unsigned and ready for use. Signed cookies reside in a different object to show developer intent; otherwise, a malicious attack could be placed on req.cookie values (which are easy to spoof). Note that signing a cookie does not make it “hidden” or encrypted; this simply prevents tampering (because the secret used to sign is private). If no signed cookies are sent, it defaults to {}.

// Cookie: user=tobi.CP7AWaXDfAKIRfH49dQzKJx7sKzzSoPq7/AcBBRVwlI3
req.signedCookies.user
// => "tobi"

参考cookie-parser查询更多信息。

req.get(field)

读取不区分大小写的请求头首部。ReferrerReferer等价。

req.get('Content-Type');
// => 'text/plain'

req.get('content-type');
// => 'text/plain'

req.get('something');
// => undefined

此方法等价于req.header(field)

req.accepts(types)

检查给定types是否可接受(acceptable),当可以接受的时候返回最佳匹配项,如果不匹配,返回undefined(此时可以使用406 Not Acceptable)。

type值可能是单个MIME type字符串如application/json,或者扩展名json,逗号分隔列表,或者数组。当列表或者数组设置时,函数返回最佳匹配项。

// Accept: text/html
req.accepts('html');
// => 'html'

// Accept: text/*, application/json
req.accepts('html');
// => 'html'
req.accepts('text/html');
// => 'text/html'
req.accepts('json, text');
// => 'json'
req.accepts('application/json');
// => 'application/json'

// Accept: text/*, application/json
req.accepts('image/png');
req.accepts('png');
// => undefined

// Accept: text/*; q=.5, application/json
req.accepts(['html', 'json']);
req.accepts('html, json');
// => 'json'

查看accepts寻找更多信息。

req.acceptsCharsets(charset,…)

检测制定charset是否可接受

req.acceptsLanguages(lang,…)

检测制定lang是否可接受

req.acceptsEncodings(encoding,…)

检测指定encoding是否可接受

req.is(type)

检测请求的Content-Type是否匹配指定的MIME类型。

// with Content-Type: text/html; charset=utf-8
req.is('html');
req.is('text/html');
req.is('text/*');
// => true

// when Content-Type is application/json
req.is('json');
req.is('application/json');
req.is('application/*');
// => true

req.is('html');
// => false

查看type-is寻找更多信息。

req.ip

返回远程地址(或者trust proxy激活时,upstream address)

req.ip;
// => '127.0.0.1'

req.ips

trust proxytrue,解析X-Forwarded-For获取ip地址列表,否则值为空数组。

req.path

返回请求URL的pathname

// example.com/users?sort=desc
req.path
// => '/users'

req.hostname

返回Host头包含的hostname值

// Host: example.com:3000
req.hostname
// => example.com

req.fresh

检测请求是否新鲜。(Last-Modified或者ETag是否匹配)

req.fresh
// => true

查看fresh寻找更多信息

req.stale

检查请求是否stalesLast-ModifiedETag是否匹配)

req.stale
// => true

req.xhr

检查X-Requested-With查看是否通过XMLHttpRequest发送

req.xhr
// => true

req.protocol

Return the protocol string “http” or “https” when requested with TLS. If the “trust proxy” setting is enabled, the “X-Forwarded-Proto” header field will be trusted. If you’re running behind a reverse proxy that supplies https for you, this may be enabled.

req.protocol
// => http

req.secure

Check if a TLS connection is established. This is a short-hand for:

'https' == req.protocol;

req.subdomains

返回包含子域名的数组

// Host: tobi.ferrets.example.com
req.subdomains
// => ['ferrets', 'tobi']

req.originalUrl

包含原始的请求url,req.url包含的是去掉mount之后的路径

// GET /search?q=something
req.originalUrl
// => /search?q=something

req.baseUrl

This property refers to the URL path, on which a router instance was mounted.

var greet = express.Router();

greet.get('jp', function (req, res) {
    console.log(req.baseUrl);       // /greet
    res.send('Konichiwa!');
});

app.use('/greet', greet);       // load the router on '/greet'

如果mount使用的是模式。取值的时候获取的不是模式,是具体值

// load the router on '/gre+t' and '/hel{2}o'
app.use(['/gre+t', '/hel{2}o'], greet);

请求/greet/jp时,baseUrl为/greet,请求/hello/jp时baseUrl为/hello

req.baseUrl is similar to the mountpath property of the app object, except app.mountpath returns the matched path pattern(s).

Response

res.status(code)

node的res.statusCode等价。用于设置HTTP响应状态码。

res.status(403).end();
res.status(400).send('Bad Request');
res.status(404).sendFile('/absolute/path/to/404.png');

res.set(field, [value])

设置响应头field值,或者传递对象设置多个field。

res.set('Content-Type', 'text/plain');

res.set({
    'Content-Type': 'text/plain',
    'Content-Length': '123',
    'ETag': '12345'
});

res.header(field, [value])等价。

res.get(field)

读取不区分大小写的响应头field

res.get('Content-Type');
// => 'text/plain'

res.cookie(name, value, [options])

设置cookienamevaluevalue可以是字符串或者对象,path选项默认值为/

res.cookie('name', 'tobi', {
    domain: '.example.com',
    path: '/admin',
    secure: true
});

res.cookie('rememberme', '1', {
    expires: new Date(Date.now() + 900000),
    httpOnly: true
});

maxAge选项用于设置expires相对于当前时间的毫秒数。

res.cookie('rememberme', '1', {
    maxAge: 900000,
    httpOnly: true
});

也可以传递对象,最终序列为JSON,然后bodyParser()自动解析

res.cookie('cart', {
    items: [1, 2, 3]
});

res.cookie('cart', {
    items: [1, 2, 3],
}, {
    maxAge: 900000
});

这种方法也可以设置signed cookie。只需要设置signed选项。When given res.cookie() will use the secret passed to cookieParser(secret) to sign the value.

res.cookie('name', 'tobi', {
    signed: true
});

然后通过访问req.signedCookie获取信息

res.clearCookie(name, [options])

清除cookie name对应的值,path默认为/

res.cookie('name', 'tobi', {
    path: '/admin'
});
res.clearCookie('name', {
    path: '/admin'
});

res.redirect([status], url)

express将设置的url字符串直接设置到Location头部,不进行验证。浏览器读取该头部并进行重定向

设置重定向url,状态码默认为302.

res.redirect('/foo/bar');
res.redirect('http://example.com');
res.redirect(301, 'http://example.com');
res.redirect('../login');

可以通过设置一个完整的url重定向到不同的网站

res.redirect('http://google.com');

重定向可以相对于主机的跟路径,例如当前在http://example.com/admin/post/new设置重定向url为/admin会重定向到http://example.com/admin

res.redirect('/admin');

重定向可以相对于当前URL,从http://example.com/blog/admin/(注意末尾的斜线)重定向到post/new得到的最终地址为http://example.com/blog/admin/post/new

res.redirect('post/new');

如果没有后面的斜线http://example.com/blog/admin那么post/new会重定向到http://example.com/blog/post/new

如果对以上行为感到困惑,可以将路径看做目录和文件,这样就好理解了。

同时也支持路径的相对重定向。http://example.com/admin/post/new可以通过以下方法重定向到http://example.com/admin/post

res.redirect('..');

也可以通过设置重定向实现后退效果。express使用referer进行设置,如果没有这个值,使用默认值/

res.redirect('back');

res.location

设置location报头

res.location('/foo/bar');
res.location('foo/bar');
res.location('http://example.com');
res.location('../login');
res.location('back');

这里的url规则和res.redirect()相同

res.send([body])

发送响应

res.send(new Buffer('whoop'));
res.send({some: 'json'});
res.send('<p>some html</p>');
res.status(404).send('sorry, can not find it');
res.status(500).send({error: 'something blew up'});

这个函数对于非流式响应会自动设置Content-Length(没有在其他地方设置时)并且提供HTTP缓存支持。

发送Buffer时,如果其他地方没有设置,Content-Type设置为application/octet-stream

res.set('Content-Type', 'text/html');
res.send(new Buffer('<p>some html</p>'));

返回字符串时Content-Type设置为text/html

res.send('<p>some html</p>');

返回数组或者对象时,默认设置JSON

res.send({user: 'tobi'});
res.send([1, 2, 3]);

res.json([body])

返回json响应,参数为对象或者数组时与res.send()等价。对于其他情况下使用这个方法可以明确设置对应报头。

res.json(null);
res.json({user: 'tobi'});
res.status(500).json({error: 'message'});

res.jsonp([body])

返回一个JSONP响应。使用方法与res.json()相同,不同的是返回结果会加上回调函数名字以支持JSONP,默认函数名为callback,可以通过app.set('jsonp callback name', 'aaa')设置与请求约定的回调名参数。

res.jsonp(null);
res.jsonp({
    user: 'tobi'
});
res.status(500)
    .jsonp(
        {
        error: 'message'
    });
// ?callback=foo
res.jsonp({user: 'tobi'});
// => foo({'user': 'tobi'})

app.set('jsonp callback name', 'cb');

// ?cb=foo
res.status(500).jsonp({error: 'message'});
// => foo({'error': 'message'})

res.type(type)

设置Content-Type,根据后缀名查询对应值,如果包含/那直接设置为对应结果。

res.type('.html');
res.type('html');
res.type('json');
res.type('application/json');
res.type('png');

res.format(object)

太长不想翻了查看res.format(object)

res.attachment([filename])

设置Content-Disposition报头为attachment,如果指定了filename,Content-Type也会根据扩展名进行设置,同时Content-Disposition的filename=参数也会设置。

res.attachment();
// Content-Disposition: attachment

res.attachment('path/to/logo.png');
// Content-Disposition: attachment; filename="logo.png"
// Content-Type: image/png

res.sendFile(path, [options], [fn])

res.sendFile需要express版本高于4.8.0

太长res.sendFile()

res.sendStatus(statusCode)

设置HTTP状态码并且将响应体设置为对应描述字符

res.sendStatus(200);
// equivalent to res.status(200.send('OK'))

res.sendStatus(403);
// equivalent to res.status(403).send('Forbidden');

res.sendStatus(404);
// equivalent to res.status(404).send('Not Found')

如果设置了位置状态码。HTTP状态码也会设置为指定值,描述字符串也于指定值相同

res.sendStatus(2000);
// res.status(2000).send('2000');

HTTP 状态码

res.download(path, [filename], [fn])

设置下载res.download()

res.links(links)

设置Link响应头

res.links({
  next: 'http://api.example.com/users?page=2',
  last: 'http://api.example.com/users?page=5'
});

结果

Link: &lt;http://api.example.com/users?page=2&gt;; rel="next",
      &lt;http://api.example.com/users?page=5&gt;; rel="last"

res.locals

设置本地变量,只能在请求/响应周期内的视图渲染可见。具体操作与app.locals相同

这个对象在暴露请求级别信息时非常有用,如请求路径名,验证信息,用户设置等。

app.use(function (req, res, next) {
    res.locals.user = req.user;
    res.locals.authenticated = !req.user.anonymous;
    next();
});

res.render(view, [locals], callback)

渲染指定的视图,出现错误时内部调用next(err)。提供的回调函数会产地错误和渲染后的字符串作为参数,此时不会自动发送响应。

res.render('index', function (err, html) {
    // ...
});

res.render('user', {name: 'Tobi'}, function (err, html) {
    // ...
});

res.vary(field)

在响应报头没有设置的情况下添加该报头。

res.vary('User-Agent').render('docs');

res.end([data], [encoding])

继承自node的http.serverResponse,用于结束响应。唯一推荐的用处是不发送数据而快速结束响应。如果想要发送数据,推荐使用res.send(),res.json()等。

res.end();
res.status(404).end();

res.headersSent

用于检测HTTP投是否已经发送

app.get('/', function (req, res) {
    console.log(res.headersSent);       // false
    res.send('OK');
    console.log(res.headersSent);       // true
});

Router

路由是Router或者中间件的实例。路由可以看做微型应用,智能用于执行中间件和路由函数。每一个express应用程序有一个内置的app router。

路由表现上与中间件相似,可以在app或者其他路由上通过.user()进行使用。

一下方法创建一个路由:

var router = express.Router([options]);

可选的选项可以修改路由行为

  • caseSensitive强制区分大小写。默认不区分,/Foo/foo一样
  • strict激活严格路由,默认情况下/foo/foo/认为相同
  • mergeParams,默认值为false确保来自父路由的req.params得到保留。如果父路由和子路由有参数冲突。子路由参数优先级更高。
// invoked for any requrests passed to this router
router.use(function (req, res, next) {
    // .. some logic here.. like any other middleware
    next();
});

// will handle any request that ends in /events
// depends on where the router is user()
router.get('/events', function (req, res, next) {
    // ..
});

可以将路由设置到对应的url上针对特定url进行响应

// only requests to /calendar/* will be sent to our router
app.use('/calendar', router);

router.use([path], [function..], function)

类似于app.use()

使用中间件处理请求。mount路径默认为’/’

var express = require('express');
var app = express();
var router = express.Router();

// simple logger for this router's requests
// all requests to this router will first hit this middleware
router.use(function (req, ers, next) {
    router.log('%s % %s', req.method, req.url, req.path);
    next();
});

// this will only be invoked if the path starts with /bar front the mount point
router.use('/bar', function (req, res, next) {
    // maybe some additional operation
    next();
});

// always invoked
router.use(function (req, res, next) {
    res.send('Hello World');
});

app.use('/foo', router);

app.listen(3000);

mount路径在传递给中间件时会自动去掉。这样产生的效果是中间件可以不关心具体路径前缀。

router.use()注册中间件的顺序非常重要,所有中间件按照顺序调用。

var logger = require('morgan');

router.use(logger());
router.use(express.static(__dirname + '/public'));
router.use(function (req, res) {
    res.send('Hello');
});

假设想要忽略静态文件的log,但是后续的中间件需要日志。可以将静态文件中间件放到前面。

router.use(express.static(__dirname + '/public'));
router.use(logger());
router.use(function (req, res) {
    res.send('Hello');
});

另一个使用场景是在多个目录提供静态文件访问。express会按照顺序遍历目录来提供文件。

app.use(express.static(__dirname + '/public'));
app.use(express.static(__dirname + '/files'));

router.param([name], callback)

为路由参数映射逻辑。例如:user出现在路由路径的时候可以映射用户加载和验证操作。

  • 回调函数是定义它的路由的本地函数。不会被mount的app或者路由继承。因此回调只会在定义它的路由上进行触发
  • 在一个请求响应周期中回调函数只执行一次,即使参数匹配多个路由

    app.param('id', function (req, res, next, id) {
        console.log('Called only once');
        next();
    });
    
    app.get('/user/:id', function (req, res, next) {
        console.log('although this matches');
        next();
    });
    
    app.get('/user/:id', function (req, res) {
        console.log('and thsi matches too');
        res.end();
    });
    

    回调函数与中间件类似,但是多了一个参数,用于对应请求实际参数。也可以从req.params获取,可以加载用户之后添加到req.user进行保存

router.route(path)

返回对应路径的一个路由实例,然后针对路由设置不同HTTP方法的逻辑。这样可以有效避免多次对同一个路径重复添加不同方法的中间件。

var router = express.Router();

router.param('user_id', function (req, res, next, id) {
    // sample user, would actually fetch from DB, etc..
    req.user = {
        id: id,
        name: 'tj'
    };
    next();
});

router.route('/users/:user_id')
    .all(function (req, res, next) {
        // runs for all HTTP verbs first
        // think of it as route specific middleware
    })
    .get(function (req, res, next) {
        res.json(req.user);
    })
    .put(function (req, res, next) {
        // just an example of maybe updating the user
        req.user.name = req.params.name;
        // save user ... etc
        res.json(req.user);
    })
    .post(function (req, res, next) {
        next(new Error('not implemented'));
    })
    .delete(function (req, res, next) {
        next(new Error('not implemented'));
    });

通过以上方法可以重用/users/:user_id来添加多个HTTP方法响应

router.METHOD(path, [callback…], callback)

为路由制定HTTP方法响应处理。

router.all(path, [callback…], callback)

为路由制定响应所有HTTP方法的中间件

FAQ

应该如何组织应用程序

这个问题没有确定的答案。取决于你程序的规模和开发团队。为了达到尽量的灵活,Express不对结构进行假设。

路由和其他程序相关的逻辑可以根据你的喜好放到任意目录和文件中。可以查看下面的例子来获得灵感:

同样存在一些第三方扩展,用于简化模式

应该如何定义model

express没有数据库的概念。这完全交给第三方模块,这样允许你与任何数据库交互。

可以查看LoopBack

应该如何验证用户

Express没有限制,你可以使用你希望的任意方法。这里是一个简单的用户名/密码模式:查看例子

express支持哪些模板引擎

express支持所有符合(path, locals, callback)签名的模板引擎。如果想规格化模板引擎接口和缓存,查看consolidate.js

如何从多个目录提供静态文件访问

通常可以多次使用任意的中间件。如下代码中,如果请求GET /js/jquery.js./public/js/jquery.js没有找到,它继续尝试./files/js/jquery.js

app.use(express.static('public'));
app.use(express.static('files'));

我应该如何为静态文件添加路径前缀

connect的”mounting”特性允许你定义路径前缀。这样就好像前缀字符串没有出现在路径中一样。假设你需要访问GET /files/js/jquery.js。你可以在/files目录mount中间件,暴露/js/jquery.js作为url。代码如下:

app.use('/files', express.static('public'));

如何处理404

express中,404不是错误的结果。因此,错误处理中间件不会捕获404.因为404表明还有工作没有完成,也就是说,express执行了所有中间件/路由,但是没有找到对应的响应处理。你需要在末尾添加一个中间件来处理404:

app.use(function (req, res, next) {
    res.send(404,, 'Sorry can not find that');
});

如何设置错误处理程序

像定义其他中间件一样定义错误处理程序。唯一的区别是这个程序期望参数为四个(err, req, res, next),查看错误处理寻找更多信息。

app.use(function (err, req, res, next) {
    console.error(err.stack);
    res.send(500, 'something broke');
});

如何

    原文作者:fe_freelancer
    原文地址: https://segmentfault.com/a/1190000002812451
    本文转自网络文章,转载此文章仅为分享知识,如有侵权,请联系博主进行删除。
点赞