Express框架
Table of Contents
声明
学习自 阮一峰老师的教程
安装
创建工程目录
mkdir test cd test
配置
在项目根目录新建文件 package.json, 添加基本配置.
{
"name": "hello-world",
"description": "hello world test app",
"version": "0.0.1",
"private": true,
"dependencies": {
"express": "4.x"
}
}
安装
npm install
启动文件
在项目根目录新建文件 index.js, 作为启动文件.
var express = require('express');
var app = express();
app.get('/', function (req, res) {
res.send('Hello world');
});
app.listen(3000);
运行.
node index
打开浏览器, 输入地址: http://localhost:3000 , 网页将会显示Hello world.
合理的结构
合理的目录结构至关重要, 方便项目管理.
路由(用于指定不同访问路径所对应的回调函数)应该放在一个单独的目录中. 新建 routes 子目录, 创建文件 index.js, 编辑如下:
module.exports = function (app) {
app.get('/', function (req, res) {
res.send('Hello world');
});
app.get('/customer', function(req, res){
res.send('customer page');
});
app.get('/admin', function(req, res){
res.send('admin page');
});
};
而原本的 index.js 文件则修改如下:
var express = require('express');
var app = express();
var routes = require('./routes')(app);
app.listen(3000);
运行原理
底层: http模块
Node.js 中 http模块 生成服务器的代码如下:
var http = require("http");
var app = http.createServer(function(request, response) {
response.writeHead(200, {"Content-Type": "text/plain"});
response.end("Hello world!");
});
app.listen(3000, "localhost");
代码的关键是 http.createServer(), 表示生成一个HTTP服务器实例. 该方法接受一个回调函数, 参数分别代表HTTP请求和HTTP回应的request对象和response对象.
而 Express 框架对其进行了再包装, 上面的代码用 Express 改写如下:
var express = require('express');
var app = express();
app.get('/', function (req, res) {
res.send('Hello world!');
});
app.listen(3000);
原来用 http.createServer() 方法创建的app实例, 现在改成用 Express 的构造方法来生成. Express框架 等于是在 http模块 上加了一个中间层.
中间件
中间件是处理HTTP请求的函数. 它的特点是, 一个中间件处理完后, 才会传递给下一个中间件处理. 一种清晰的写法如下:
var express = require("express");
var http = require("http");
var app = express();
app.use("/home", function(request, response, next) {
response.writeHead(200, { "Content-Type": "text/plain" });
response.end("Welcome to the homepage!\n");
});
app.use("/about", function(request, response, next) {
response.writeHead(200, { "Content-Type": "text/plain" });
response.end("Welcome to the about page!\n");
});
app.use(function(request, response) {
response.writeHead(404, { "Content-Type": "text/plain" });
response.end("404 error!\n");
});
http.createServer(app).listen(1337);
Express的方法
all()和HTTP动词方法
因为HTTP有多种请求, 如: GET, POST, PUT, DELETE, 为了使程序更加清晰, Express框架不建议统一使用 use(), 它提供了 use() 方法的一些别名, 根据不同的请求进行调用. 因此, 上面的代码还可以改成如下形式:
var express = require("express");
var http = require("http");
var app = express();
app.all("*", function(request, response, next) {
response.writeHead(200, { "Content-Type": "text/plain" });
next();
});
app.get("/", function(request, response) {
response.end("Welcome to the homepage!");
});
app.get("/about", function(request, response) {
response.end("Welcome to the about page!");
});
app.get("*", function(request, response) {
response.end("404!");
});
http.createServer(app).listen(1337);
all() 表示, 所有请求都必须通过该中间件, 参数中的 * 表示对所有路径都有效. 这样其他的中间件可以省去很多重复的代码. get() 表示只有HTTP请求方式为GET时, 才通过该中间件, 它的第一个参数是请求的路径, 由于 get() 的回调函数没有调用 next(), 所以只要有一个中间件被调用了, 后面的中间件就不会再被调用.
对于请求的路径, 除了使用绝对匹配外, 还可以模式匹配. 如:
app.get("/hello/:who", function(req, res) {
res.end("hello, " + req.params.who + ".");
});
上面的代码可以匹配"/hello/alice"网址, 网址中的alice将被捕获, 作为 req.params.who 属性的值. 需要注意的是, 捕获后一般需要对网址进行检查, 过滤不安全字符, 上面的写法只是为了演示, 实际生产中不应该这样直接使用用户提供的值.
如果在模式参数后面加上问号, 表示该参数可选.
app.get('/hello/:who?',function(req,res) {
if(req.params.id) {
res.end("Hello, " + req.params.who + ".");
}
else {
res.send("Hello, Guest.");
}
});
更复杂的例子:
app.get('/forum/:fid/thread/:tid', middleware)
// 匹配/commits/71dbb9c
// 或/commits/71dbb9c..4c084f9这样的git格式的网址
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);
});
set方法
用于指定变量的值.
app.set("views", __dirname + "/views");
app.set("view engine", "jade");
该代码使用 set(), 为系统变量"views"和"view engin"指定值.
response对象
response.redirect(): 网址重定向. 如: response.redirect("/hello/anime"); response.sendFile(): 发送文件. 如: response.sendFile("/path/to/anime.mp4"); response.render(): 渲染网页模板. 如:
app.get("/", function(request, response) {
response.render("index", { message: "Hello World" });
});
该代码使用 render() 方法, 把 message 变量传入index模板, 渲染成HTML网页.
request对象
request.ip: 属性, 用于获得HTTP请求的IP地址. request.files: 用于获取上传的文件.
搭建HTTPs服务器
使用Express搭建HTTPs加密服务器.
var fs = require('fs');
var options = {
key: fs.readFileSync('E:/ssl/myserver.key'),
cert: fs.readFileSync('E:/ssl/myserver.crt'),
passphrase: '1234'
};
var https = require('https');
var express = require('express');
var app = express();
app.get('/', function(req, res){
res.send('Hello World Expressjs');
});
var server = https.createServer(options, app);
server.listen(8084);
console.log('Server is running on port 8084');
项目开发实例
首先创建工程目录, 配置, 配置文件如下:
{
"name": "demo",
"description": "My First Express App",
"version": "0.0.1",
"dependencies": {
"express": "3.x"
}
}
安装, 编写启动文件 app.js. 内容如下:
var express = require('express');
var path = require('path');
var app = express();
// 设定port变量,意为访问端口
app.set('port', process.env.PORT || 3000);
// 设定views变量,意为视图存放的目录
app.set('views', path.join(__dirname, 'views'));
// 设定view engine变量,意为网页模板引擎
app.set('view engine', 'jade');
app.use(express.favicon());
app.use(express.logger('dev'));
app.use(express.bodyParser());
app.use(express.methodOverride());
app.use(app.router);
// 设定静态文件目录,比如本地文件
// 目录为demo/public/images,访问
// 网址则显示为http://localhost:3000/images
app.use(express.static(path.join(__dirname, 'public')));
app.listen(app.get('port'));
set() 用于设定内部变量, use() 用于调用express的中间件.
在浏览器中访问: http://localhost:3000 , 网页提示"Cannot GET /", 表示没有为网站的根路径指定可以显示的内容. 所以下一步开始配置路由.
配置路由
所谓"路由", 就是指为不同的访问路径, 指定不同的处理方法.
在 app.js 中, 指定根路径的处理方法.
app.get('/', function(req, res) {
res.send('Hello World');
});
再通过浏览器访问, 就会显示"Hello World".
如果需要指定HTTP头信息, 回调函数就必须换一种写法:
app.get('/', function(req, res){
var body = 'Hello World';
res.setHeader('Content-Type', 'text/plain');
res.setHeader('Content-Length', body.length);
res.end(body);
});
指定特定路径
假定用户访问 /api路径, 希望返回一个JSON字符串, 可以这么写:
app.get('/api', function(request, response) {
response.send({name:"张三",age:40});
});
为了便于管理, 我们把路由的回调函数, 封装成模块, 在 routes目录 下建立一个 api.js文件.
exports.index = function (req, res) {
res.json(200, {name:"张三", age:40});
}
然后 在 app.js 中加载这个模块:
var api = require('./routes/api');
app.get('/api', api.index);
此时, 在浏览器中访问 http://localhost:3000/api 就会有正确的文字显示出来.
静态网页模板
在项目目录中, 建立一个子目录 views, 用于存放网页模板. 假设该项目有三个路径: 根路径(/), 自我介绍(/about), 文章(/article). 修改 app.js 的中间件:
app.get('/', function (req, res) {
res.sendfile(__dirname + '/views/index.html');
});
app.get('/about', (req, res) => {
res.sendfile(__dirname + '/views/about.html');
});
app.get('/article', (req, res) => {
res.sendfile(__dirname + '/views/article.html');
});
然后编辑 views/index.html:
<html> <head> <title>首页</title> </head> <body> <h1>Express Demo</h1> <footer> <p> <a href="/">首页</a> - <a href="/about">自我介绍</a> - <a href="/article">文章</a> </p> </footer> </body> </html>
如果想要展示动态内容, 就必须使用动态网页模板.
动态网页模板
安装模板引擎
Express支持多种模板引擎, 这里使用Handlebars模板引擎的服务器端版本.
npm install hbs --save-dev
save-dev 表示将依赖关系写入 package.json 文件.
安装完成后, 需要改写 app.js:
var express = require('express');
var hbs = require('hbs');
var app = express();
// 指定模板文件的后缀名为html
app.set('view engine', 'html');
// 运行hbs模块
app.engine('html', hbs.__express);
app.get('/', function (req, res) {
res.render('index');
});
app.get('/about', function (req, res) {
res.render('about');
});
app.get('/article', function (req, res) {
res.render('article');
});
上面的代码改用 render() 对网页模板进行渲染. render() 的参数就是模板的文件名, 默认放在子目录 views 之中, 后缀名已经在前面指定为html, 这里可以省略. 所以, res.render('index') 是指: 把子目录views下面的index.html文件, 交给模板引擎hbs渲染.
新建数据脚本
渲染是指将数据代入模板的过程. 在实际应用中, 数据是保存在数据库的, 这里为简化问题, 假定数据保存在一个脚本文件中.
在项目目录中, 新建一个文件 blog.js, 用于存放数据.
var entries = [
{"id":1, "title":"第一篇", "body":"正文", "published":"6/2/2013"},
{"id":2, "title":"第二篇", "body":"正文", "published":"6/3/2013"},
{"id":3, "title":"第三篇", "body":"正文", "published":"6/4/2013"},
{"id":4, "title":"第四篇", "body":"正文", "published":"6/5/2013"},
{"id":5, "title":"第五篇", "body":"正文", "published":"6/10/2013"},
{"id":6, "title":"第六篇", "body":"正文", "published":"6/12/2013"}
];
exports.getBlogEntries = function (){
return entries;
}
exports.getBlogEntry = function (id){
for(var i=0; i < entries.length; i++){
if(entries[i].id == id) return entries[i];
}
}
新建网页模板
在目录 views 里新建模板文件 index.html.
<!-- views/index.html文件 -->
<h1>文章列表</h1>
{{#each entries}}
<p>
<a href="/article/{{id}}">{{title}}</a><br/>
Published: {{published}}
</p>
{{/each}}
模板文件about.html:
<!-- views/about.html文件 --> <h1>自我介绍</h1> <p>正文</p>
模板文件article.html:
<!-- views/article.html文件 -->
<h1>{{blog.title}}</h1>
Published: {{blog.published}}
<p/>
{{blog.body}}
以上三个模板文件都只有网页主体, 因为网页布局是共享的, 所以布局的部分可以单独新建一个文件layout.html:
<!-- views/layout.html文件 -->
<html>
<head>
<title>{{title}}</title>
</head>
<body>
{{{body}}}
<footer>
<p>
<a href="/">首页</a> - <a href="/about">自我介绍</a>
</p>
</footer>
</body>
</html>
渲染模板
改写 app.js:
var express = require('express');
var hbs = require('hbs');
var app = express();
// 加载数据模块
var blogEngine = require('./blog');
// 指定模板文件的后缀名为html
app.set('view engine', 'html');
// 运行hbs模块
app.engine('html', hbs.__express);
app.use(express.bodyParser());
app.get('/', function (req, res) {
res.render('index', {title:"最近文章", entries:blogEngine.getBlogEntries()});
});
app.get('/about', function (req, res) {
res.render('about', {title:"自我介绍"});
});
app.get('/article/:id', function (req, res) {
var entry = blogEngine.getBlogEntry(req.params.id);
res.render('article', {title:entry.title, blog:entry});
});
app.listen(3000);
此时可以用浏览器访问.
指定静态文件目录
模板文件默认存放在 views子目录. 这时, 如果要在网页中加载静态文件(如样式表, 图片等), 就需要另外指定一个存放静态文件的目录.
app.use(express.static('public'));
当浏览器发出非HTML文件请求时, 服务器就到 public 目录寻找这个文件, 比如浏览器发出如下的样式表请求:
<link href="/bootstrap/css/bootstrap.css" rel="stylesheet">
服务器就到 public/bootstrap/css 目录中寻找 bootstrap.css 文件.
Express.Router用法
从Express 4.0开始, 路由器功能成了一个单独的组件 Express.Router, 它就像小型的express应用程序一样, 有自己的use, get, param, route方法.
基本用法
Express.Router 是一个构造函数, 调用后返回一个路由器实例. 再使用该实例的HTTP动词方法, 为不同的访问路径, 指定回调函数, 最后挂载到某个路径.
var router = express.Router();
router.get('/', function (req, res) {
res.send('首页');
});
router.get('/about', function (req, res) {
res.send('关于');
});
app.use('/', router);
app.use('/', router) 表示将之前定义的路径挂载到根目录.
如果改成 app.use('/app', router) 表示将之前定义的路径挂载到 '/app' 目录, 相当于 '/app' 和 '/app/about' 这两个路径.
router.route()
使用 router.route(), 可以直接将访问路径作为参数, 且可以对同一个路径指定get和post方法的回调函数.
var router = express.Router();
router.route('/api')
.post (function (req, res) { ... })
.get (function (req, res) { ... });
app.use('/', router);
router中间件
router.use(function (req, res, next) {
console.log(req.method, req.url);
next();
});
中间件的放置顺序很重要, 必须放在HTTP动词方法之前, 等同于执行顺序.
对路径参数的处理
router.param('name', function(req, res, next, name) {
// 对name进行验证或其他处理……
console.log(name);
req.name = name;
next();
});
router.get('/hello/:name', function(req, res) {
res.send('hello ' + req.name + '!');
});
上面代码中, get方法为访问路径指定了name参数, param方法则是对name参数进行处理. 注意, param方法必须放在HTTP动词方法之前.
app.route
推荐这种写法:
var app = express();
app.route('/login')
.get(function(req, res) {
res.send('this is the login form');
})
.post(function(req, res) {
console.log('processing');
res.send('processing the login form!');
});
上传文件到本地目录
在网页插入上传文件的表单.
<form action="/pictures/upload" method="POST" enctype="multipart/form-data"> Select an image to upload: <input type="file" name="image"> <input type="submit" value="Upload Image"> </form>
服务器脚本建立指向 /upload 目录的路由. 可以安装 multer模块, 它提供了上传文件的许多功能.
var express = require('express');
var router = express.Router();
var multer = require('multer');
var uploading = multer({
dest: __dirname + '../public/uploads/',
// 设定限制,每次最多上传1个文件,文件大小不超过1MB
limits: {fileSize: 1000000, files:1},
})
router.post('/upload', uploading, function(req, res) {
})
module.exports = router
上传文件到Amazon S3
在S3上面新增CORS配置文件.
<?xml version="1.0" encoding="UTF-8"?>
<CORSConfiguration xmlns="http://s3.amazonaws.com/doc/2006-03-01/">
<CORSRule>
<AllowedOrigin>*</AllowedOrigin>
<AllowedMethod>GET</AllowedMethod>
<AllowedMethod>POST</AllowedMethod>
<AllowedMethod>PUT</AllowedMethod>
<AllowedHeader>*</AllowedHeader>
</CORSRule>
</CORSConfiguration>
上面的配置允许任意电脑向你的bucket发送HTTP请求.
然后安装 aws-sdk:
npm install aws-sdk --save
安装服务器脚本:
var express = require('express');
var router = express.Router();
var aws = require('aws-sdk');
router.get('/', function(req, res) {
res.render('index')
})
var AWS_ACCESS_KEY = 'your_AWS_access_key'
var AWS_SECRET_KEY = 'your_AWS_secret_key'
var S3_BUCKET = 'images_upload'
router.get('/sign', function(req, res) {
aws.config.update({accessKeyId: AWS_ACCESS_KEY, secretAccessKey: AWS_SECRET_KEY});
var s3 = new aws.S3()
var options = {
Bucket: S3_BUCKET,
Key: req.query.file_name,
Expires: 60,
ContentType: req.query.file_type,
ACL: 'public-read'
}
s3.getSignedUrl('putObject', options, function(err, data){
if(err) return res.send('Error with S3')
res.json({
signed_request: data,
url: 'https://s3.amazonaws.com/' + S3_BUCKET + '/' + req.query.file_name
})
})
})
module.exports = router
上面代码中,用户访问/sign路径,正确登录后,会收到一个JSON对象,里面是S3返回的数据和一个暂时用来接收上传文件的URL,有效期只有60秒。
浏览器代码如下。
// HTML代码为
// <br>Please select an image
// <input type="file" id="image">
// <br>
// <img id="preview">
document.getElementById("image").onchange = function() {
var file = document.getElementById("image").files[0]
if (!file) return
sign_request(file, function(response) {
upload(file, response.signed_request, response.url, function() {
document.getElementById("preview").src = response.url
})
})
}
function sign_request(file, done) {
var xhr = new XMLHttpRequest()
xhr.open("GET", "/sign?file_name=" + file.name + "&file_type=" + file.type)
xhr.onreadystatechange = function() {
if(xhr.readyState === 4 && xhr.status === 200) {
var response = JSON.parse(xhr.responseText)
done(response)
}
}
xhr.send()
}
function upload(file, signed_request, url, done) {
var xhr = new XMLHttpRequest()
xhr.open("PUT", signed_request)
xhr.setRequestHeader('x-amz-acl', 'public-read')
xhr.onload = function() {
if (xhr.status === 200) {
done()
}
}
xhr.send(file)
}
上面代码首先监听file控件的change事件,一旦有变化,就先向服务器要求一个临时的上传URL,然后向该URL上传文件。
Generated by Emacs 25.x(Org mode 8.x)
Copyright © 2014 - pinvon - Powered by EGO