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