#1 middlewares, route, views, request 和response对象

Express是建立在NODEJS http模块基础上的网络服务框架,具有以下几个特点:

  • 使用JavaScript作为开发语言,实现前端后端语言的一致性
  • 最大特点:中间件 和 路由
  • 灵活性,由于采用模块化的理念,可以根据需求对服务进行组装

express的基本概念,可以参见express官网, 其核心内容很少。下面是自己学习的一些笔记。

一.第三方middleware库

morgan: LOGGING MIDDLEWARE

这个用于记录日志使用,可用于记录用户操作,服务端响应请求requests花费时间,用作性能分析。

安装:

npm install --save morgan

使用:

var express = require('express');
var http = require('http');
// 引入 Morgan 模块
var logger = require('morgan');

var app = express();

// 使用morgan中间件, 并规定输出格式
// 还可以设置为其它选项,比如 app.use(logger('dev'));
app.use(logger('short'));

app.use(function(req, res) {
    res.writeHead(200, { 'Content-Type': 'text/plain' });
    res.end('hello express');
});

http.createServer(app).listen(3000);

// 登录 localhost:3000
// console中显示
::1 - GET / HTTP/1.1 200 - - 0.621 ms

express.static

这个是express自身封装的一个中间件,所以无需从npm中安装。这个中间件用于发送静态资源,比如images, CSS 或HTML文件等。

假如应用的静态资源存放在 public 目录下,则可以通过此中间件发送文件:

var path = require('path');

// 使用NODE的 path模块设置 public 路径
var publicPath = path.resolve(__dirname, 'public');

// 声明 'public' 目录为静态文件夹
app.use(express.static(publicPath));

app.use(function(req, res) {
    res.writeHead(200, { 'Content-Type': 'text/plain' });
    res.end('没有找到该静态文件');
});
// ...

如果没有找到文件,则app将进入下一个中间件,如果找到了,express.static将发送该文件,并且停止中间件的链式操作。

值得之一的是,开发过程中,可以使用 res.sendFile() 提供静态文件,但是在生产环境中不要执行此函数,因为它必须针对每次文件请求读取文件系统,会产生严重的延迟,影响应用程序的总体性能。 建议使用 serve-static 中间件,这个中间件经过优化,用于为Express应用程序提供文件。 还有一个更好的选项,就是使用逆向代理提供静态文件。

其它

暂时先接触到这些中间件,其它的一些中间件仅作列举,以后碰到再详谈:

  • body-parser: 解析HTTP request body
  • connect-ratelimit: 限制每小时连接数量,如果某人向服务器发送大量请求,这利用这个中间件返回错误,阻止服务器宕机
  • helmet: 给应用添加 HTTP headers, 用于阻止特定类型的攻击
  • cookie-parser: 解析浏览器cookies
  • response-time: 发送 X-Response-Time header,用于调试app性能

二.Routing

路由根据 URL 和特定的 HTTP method 将请求(requests)匹配到特定的处理函数(handlers).

例如:

var express = require('express');
var http = require('http');
var path = require('path');

var app = express()
var publicPath = path.resolve(__dirname, 'public');
app.use(express.static(publicPath));

// 对于 '/' 表示根目录
// HTTP GET 请求
app.get('/', function(req, res) {
    res.end('这是主页');
});


// 动态的路径匹配 '/about/:name'
app.get('/about/:name', function(req, res) {
    res.end(`${req.params.name} 的About页面`)
});

// 如果没有匹配到,则返回404
// 使用中间件
app.use(function(req, res) {
    // 将状态码设置为 404
    res.statusCode = 404;
    res.end('没有找到该页面')
});

http.createServer(app).listen('3000');

除了可以使用 app.get(), 还可以使用其它的http动作,比如 app.post()等等。

路由的路径匹配有3中方式:

  1. 字符串
  2. 字符串模式: 其实就是正则表达式和字符串的一种结合写法
  3. 正则表达式

1.最常见的字符串:

app.get('/home', ...)
app.get('/about/company', ...)
app.get('/user/:id', ...) //  匹配 /user/1, /user/2, ....

2.字符串模式

app.get('/ab?cd', ...)   // 匹配 acd, abcd
app.get('/ab+cd', ...)   // 匹配 abcd, abbcd, abbb...cd
app.get('/ab(cd)?e',...) // 匹配 abe, abcde

3.正则表达式

app.get(/a/, ...) // 路径中所有具有 'a' 的路由
app.get(/.*fly$/, ...) // 任意以 'fly' 结尾的路由

三.request对象和response对象

这2个对象在Express中得到了扩充,在NODEJS中有些属性是没有的,下面罗列一些Express对这2个对象的扩充属性:

res.redirect([status,] path)

response对象添加了重定向方法,状态码默认为302, 有以下几种形式:

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

# 1.完全匹配URL重定向到另一个网址
res.redirect('http://google.com');

# 2.相对host根目录,比如现在在 ' http://example.com/admin/post/new'
// 下面可以重定向到 'http://example.com/admin'
res.redirect('/admin')

# 3.可以相对当前目录,比如现在在 'http://example.com/blog/admin/'(注意最后有 '/')
// 下面可以重定向到 ' http://example.com/blog/admin/post/new'
res.redirect('post/new');
// 如果当前在 'http://example.com/blog/admin'(注意最后没有 '/')
// 下面可以重定向到 ' http://example.com/blog/post/new'

# 4.相对路径也是可能的,比如现在在 'http://example.com/admin/post/new'
// 下面可以重定向到 ' http//example.com/admin/post'
res.redirect('..')

# 5.'back' 返回到 referer; 如果referer不存在,默认为 '/'
res.redirect('back')

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

Express v4.8.0版本以上支持此方法。

发送指定path的文件, path 必须是一个绝对路径。 Content-Type 根据文件的扩展设置。

options具体选项参见文档 sendFile()

fn 必须是通过终止request-response cycle或将控制传递给下一个route

app.get('/file/:name', function(req, res, next) {
    var options = {
        root: __dirname + '/public',
        dotfiles: 'deny',
        headers: {
            'x-timestamp': Date.now(),
            'x-send': true
        }
    };

    var fileName = req.params.name;

    res.sendFile(fileName, options, function(err) {
        if (err) {
            next(err);
        } else {
            console.log('Send: ' + fileName);
        }
    });
});

res.status(code)
设置响应的 HTTP status,返回值可链式调用

res.status(403).end()
res.status(400).send('BAD REQUEST')
res.status(404).sendFile('/absolute/path/to/404.html')

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

渲染view,并且发送已渲染的html字符串到客户端,参数:

  • locals: 一个对象,其属性定义view中的本地变量

  • callback: 返回可能的错误和渲染的字符串,当发生错误,方法内部调用next(err)

  • view: 文件路径字符串。可以是绝对路径或相对 views 设置的相对路径。如果路径不包含文件扩展,则根据 view engine 设置来决定文件扩展

local variable cache 使view缓存。设置为true,则开发时将缓存。对产品阶段,view caching默认为true

// 发送渲染的view到客户端
res.render('index')

// 如果回调指定,则渲染的html字符串必须显式的发送
res.render('index', function(err, html) {
    res.send(html);
});

// 传递一个local变量给view
res.render('user', { name: 'Tobi' }, function(err, html) {
    // ...
})

req.get(field)

请求新增的方法,返回特定的 HTTP request header字段(不区分大小写)

req.get('Content-Type') // 'text/plain'
req.get('something')    // undefined

req.ip

返回请求的ip地址

var EVIL_IP = '123.45.67.89';
app.use(function(req, res, next) {
    if (req.ip === EVIL_IP) {
        res.status(401).send('不许该ip访问');
    } else {
        next();
    }
});
// ...

四.Views

express 的视图可以采用多种模版来渲染,比如EJS(Embedded JavaScript), Pug, Handlebars等。

下面方式来设置视图:

// 设置视图所在文件夹为 views
app.set('views', path.resolve(__dirname, "views"))

// 设置视图渲染引擎
// 这些引擎需要自己安装 npm install --save ejs
app.set('view engine', 'ejs')

五.示例

下面是留言本的一个小网页:

// app.js

var express = require('express');
var http = require('http');
var path = require('path');
var logger = require('morgan');
var bodyParser = require('body-parser');

var app = express();

// 设置视图文件夹和渲染引擎
app.set('views', path.resolve(__dirname, 'views'));
app.set('view engine', 'ejs');

var entries = [];
app.locals.entries = entries; // 使entries在所有的views中都可用

app.use(logger('dev'));

// 如果用户提交表单。产生一个 req.body 变量
// extended 选项是必须的
app.use(bodyParser.urlencoded({ extended: false }));

// 访问根路径,返回 views/index.ejs
app.get('/', function(req, res) {
    res.render('index');
});

app.get('/new-entry', function(req, res) {
    res.render('new-entry');
});

// 定义路由处理 '/new-entry' 下的 POST 请求
app.post('/new-entry', function(req, res) {
    // 如果用户没有填写title或body,则返回400错误
    if (!req.body.title || !req.body.body) {
        res.status(400).send('Entries must have a title and body');
        return;
    }
    entries.push({
        title: req.body.title,
        content: req.body.body,
        published: new Date()
    });
    // 重定向到主页查看新的entry
    res.redirect('/');
});

// 404 page
app.use(function(req, res) {
    res.status(400).render('404');
});


http.createServer(app).listen('8888');

具体代码 express-guestbook github

总结

本章主要是Express的具体操作和部分APIs的介绍。简单的谈了Express的2大特点:middleware, route。另外还使用到视图,视图如何通过模版引擎进行渲染。

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 205,386评论 6 479
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 87,939评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 151,851评论 0 341
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,953评论 1 278
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,971评论 5 369
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,784评论 1 283
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,126评论 3 399
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,765评论 0 258
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 43,148评论 1 300
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,744评论 2 323
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,858评论 1 333
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,479评论 4 322
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,080评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,053评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,278评论 1 260
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 45,245评论 2 352
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,590评论 2 343

推荐阅读更多精彩内容