首先看一个客户端发起 http 请求到服务端返回响应结果的过程:
图源网
Node.js
为什么要使用 Node.js?
Node.js 的目的是实现动态网页,也就是说由服务器动态生成 html 页面.之所以要这么做,是因为静态 HTML 的可扩展性非常有限,无法与用户有效交互,同时如果有大量相似的内容,例如产品介绍页面,那么 1000 个产品就要 1000 个静态的 HTML 页面,维护这 1000 个页面简直是一场灾难,因此动态生成 HTML 页面的技术应运而生.
Node.js 可跳过 HTTP 服务器,因为它本身就是,Node.js 提供了 http 模块,它是由 C++ 实现的,性能可靠,可以直接应用到生产环境.
Node.js 和其他的语言相比的另一个显著区别,在于它的原始封装程度较低.在 Node.js 中,很多工作需要你自己来做,而自己来做这些事情太过麻烦,而且也没有必要重复造轮子,所以需要第三方框架来帮助我们.
安装 Node.js
用 Node.js 创建一个应用
在没有 Express 的时候,我们要创建一个 web 应用,用 Node.js 怎样实现呢?
新建一个目录,进入此目录并初始化
$ mkdir myapp && cd myapp
$ npm init -y
创建 index.js 文件
$ touch index.js
在 index.js 文件下添加如下代码用于引入 http 模块并创建一个服务器,监听 3001 端口.
const http = require('http')
const log = console.log
const err = console.error
http.createServer((req, res) => res.end('hello world!'))
.listen(3001, () => log('server started'))
运行 node index.js,终端显示'server started',证明服务启动成功
在浏览器中打开
http://localhost:3001/
,页面显示'hello world!',至此一切正常.
但实际的应用开发中不可能只是让页面显示一句话这么简单,我们需要了解各种参数及其用法以应对更为复杂的使用场景.
上面的代码中,req 是 http.IncomingMessage 的实例,代表的是服务器接收到的请求,res 是 http.ServerResponse 的实例,代表服务器响应的结果.
req 上常见的属性有:
url: / ,/write, /post
method: get ,post ,delete ,put ,options
METHODS 支持的方法
STATUS_CODES
res 上的常见操作有:
res.end([data][,encoding][,callback])
res.getHeader(name)
getHeaders()
setHeader(name,value)
res.statusCode
res.write(chunk[,encoding][,callback])
以 req.url 为例:
http.createServer((req, res) => {
if (req.url === '/') {
res.end('hey index')
} else if (req.url === '/write') {
res.end('hey write')
} else if (req.url === '/post') {
res.end('hey post')
}
}).listen(3000, e => {
if (e) {
err(e)
} else {
log('server started')
}
})
重启 node,打开浏览器,页面内容根据 url 值的变化而变化
实际应用中大部分的 url 对应的不是简单的一句话,而是某一个文件,所以我们需要引入 fs 模块实现文件级操作.
在项目根目录下新建 static 文件夹,在 static 文件夹下新建 a.js,b.js,内容如下
# a.js
console.log('I am a.js')
# b.js
console.log('I am b.js')
注释掉 index.js 之前的所有代码,现在的代码如下:
const http = require('http')
const fs = require('fs')
const log = console.log
const err = console.error
http.createServer((req, res) => {
if (req.url === '/a.js') {
fs.createReadStream('./static/a.js').pipe(res)
} else if (req.url === '/b.js') {
fs.createReadStream('./static/b.js').pipe(res)
}
}).listen(3000, e => {
if (e) {
err(e)
} else {
log('server started')
}
})
启动 node,分别打开 http://localhost:3000/a.js 和 http://localhost:3000/b.js
可以看到页面上显示出了 a.js 和 b.js 的内容,而不是在控制台打印出 console.log 里的内容,原因在于 createReadStream() 方法用于打开文本文件创建一个读取操作的数据流,实现原理是每次发送触发一个 data 事件,发送结束触发 end 事件,即文本中的内容会被装在 res.end() 中,原样显示到对应 url 的页面上.
Express
为什么要使用 Express 作为开发框架呢?
Express 是基于 Node.js 平台,快速,开放,极简的 web 开发框架,它提供一系列强大的特性用来创建各种 Web 和移动设备应用,拥有丰富的 HTTP 快捷方法和任意排列组合的 Connect 中间件.
Express 不对 Node.js 已有的特性进行二次抽象,只是在它之上扩展了 Web 应用所需的基本功能,例如:
路由控制
路由处理器(中间件)
模板引擎
静态文件服务
错误处理
访问日志
缓存
动态视图
用户会话
CSRF 保护
插件支持
上述功能中我们用得比较多的是前 6 个.
需要说明的是 Express 只是一个轻量级的 Web 框架,多数功能只是对 HTTP 协议中常用操作的封装,更多的功能需要插件或者整合其他模块来完成.
用 Express 实现的网站实际上就是一个 Node.js 程序,因此可以直接运行.
安装
npm install express --save
路由
路由是指如何定义应用的端点(URIs)以及如何响应客户端的请求.
路由是由一个 URI,HTTP 请求(GET,POST 等)和若干个句柄组成,它的结构如下:
app.METHOD(path, [callback...], callback)
, app 是 express 对象的一个实例, METHOD 是一个 HTTP 请求方法 , path 是服务器上的路径, callback 是当路由匹配时要执行的回调函数.
基本的路由示例
# express-index.js
const express = require('express')
const app = express()
const log = console.log
const err = console.error
app.get('/',(req,res)=>res.send('hey dolby'))
app.listen(3000,()=>log('server listening at localhost:3000'))
运行
node express-index.js
启动程序
在浏览器中打开 localhost:3000
正则匹配
app.get(/.*dot$/, (req, res) => res.send('.*dot'))
匹配所有以 dot 结尾的字符串
重启服务器并打开 localhost:3000/hellodot
路由参数
app.get('/users/:userId/books/:bookId', (req, res) => res.send(req.params))
app.get('/users/:userId(\\d+)', (req, res) => res.send(req.params))
:userId 只是一个占位符,代表 users 后的参数,其后的 (\d+) 表示只接收整数参数,注意有两个 \,第一个用于转义.生命不息,采坑不止.
中间件
使用多个回调处理路由,注意回调函数中多了参数 next,也别忘了后面的 next()
app.get('/eat/:eatSomething', (req, res, next) => {
if (req.params.eatSomething === 'eatEverything') {
res.end('Hey you eat too much')
} else {
next()
}
}, function (req, res) {
res.send('Hello')
})
重启 node 并修改 url
使用回调函数数组处理路由
var mw0 = (req, res, next) => {
log('mw0')
next()
}
var mw1 = (req, res, next) => {
log('mw1')
next()
}
var mw2 = (req, res, next) => {
log('mw2')
res.send('finish')
}
app.get('/example/mw', [mw0, mw1, mw2])
重启 node 并修改 url 为 http://localhost:3000/example/mw
混合使用回调函数数组和回调函数处理路由
var mw0 = (req, res, next) => {
log('mw0')
next()
}
var mw1 = (req, res, next) => {
log('mw1')
next()
}
app.get('/example/mw', [mw0, mw1], (req, res, next) => {
log('mw2')
next()
}, (req, res) => {
log('mw3')
res.send('finish')
})
重启 node 并刷新浏览器页面
route 路由(这种基本用不到)
get 方法接收的是一个回调函数
app.route('/book')
.get((req, res) => {
res.end('hello book')
}).post(function (req, res) {
res.send('Add a book');
}).put(function (req, res) {
res.send('Update the book');
})
重启 node 并修改 url 为 http://localhost:3000/book
express.Router() 类及 app.use(挂载点, 中间件)
使用 express.Router 类创建模块化,可挂载的路由句柄.Router 实例是一个完整的中间件和路由系统,因此常称其为一个 "mini-app".
下面的实例程序创建了一个路由模块,并加载了一个中间件,定义了一些路由,并且将它们挂载至应用的路径上.
在根目录下新建 food.js
# food.js
const express = require('express')
const app = express()
const router = express.Router()
const log = console.log
const err = console.error
// 该路由使用打印时间的中间件
// 没有挂载点的中间件,应用的每个请求都会执行该中间件
router.use(function timeLog(req, res, next) {
log('Time: ', Date.now())
next()
})
// 定义主页的路由
router.get('/', (req, res) => {
res.send('Home')
})
// 定义about页面的路由
router.get('/about', (req, res) => {
res.send('About food')
})
module.exports = router
修改 express-index.js
const express = require('express')
const app = express()
const log = console.log
const err = console.error
app.listen(3000, () => log('server listening at localhost:3000'))
var food = require('./food.js')
// 挂载至 /food 的中间件,任何指向 /food 的请求都会执行它
app.use('/food', food)
重启 node 后修改 url
来源: http://www.jianshu.com/p/f9c1a31606d9