@artman328
2019-05-28T02:30:42.000000Z
字数 10862
阅读 1231
node
express
web
本文地址:https://zybuluo.com/artman328/note/1151185
Web 应用由两部分组成:一是用户用于交互的用户代理(User agent),通常是浏览器;二是用于提供信息服务的 web 服务器。双方通过 HTTP 协议进行 请求/响应 模式的通信。
HTTP/1.1 (版本 1.1)的请求和响应都是特定格式的字符串。
样例:
POST /cgi-bin/process.cgi HTTP/1.1
User-Agent: Mozilla/4.0 (compatible; MSIE5.01; Windows NT)
Host: www.tutorialspoint.com
Content-Type: application/x-www-form-urlencoded
Content-Length: 49
Accept-Language: en-us
Accept-Encoding: gzip, deflate
Connection: Keep-Alive
licenseID=string&content=string&/paramsXML=string
第 1 行为请求行,包含了请求方法(POST),请求的资源相对地址:/cgi-bin/process.cgi,以及请求所用的协议及其版本号:HTTP/1.1;
第 2 ~ 8 行为请求头,第行说明不同的内容;
第 9 行为一个空行,是请求头和请求体的分界线(有的请求没有请求体,如 GET 请求);
第 10 行为请求体。(请求体内容可以很长,头部:Content-Length 说明了请求体的字节数)。
样例:
HTTP/1.1 200 OK
Date: Mon, 27 Jul 2009 12:28:53 GMT
Server: Apache/2.2.14 (Win32)
Last-Modified: Wed, 22 Jul 2009 19:15:56 GMT
Content-Length: 88
Content-Type: text/html
Connection: Closed
<html>
<body>
<h1>Hello, World!</h1>
</body>
</html>
第 1 行为响应行,说明了协议及其版本号:HTTP/1.1,响应状态编码:200,响应结果信息:OK;
第 2 ~ 7 行为响应头;
第 8 行为空行,作为响应头与响应体的分界;
第 9 行开始为响应体。(字节数由响应头 Content-Length 决定)
HTTP 协议是无状态协议,如果要记住访问服务器的用户,则可采用 Session 和 Cookie 技术。
Cookie 是在客户端保存用户信息的方法,它要保存的信息由响应头带回来,并保存在用户的计算机中。浏览器在访问某个网址时,会将这个网址保存在本地的 Cookie 信息(如果有的话)带给服务器。这样服务器就可通过读取 Cookie 信息了解该用户了。
与 Cookie 不同,Session 是将用户信息保存到服务器的技术。服务器保存了用户信息后,会分配给该用户一个 ID ,并将该 ID 传回客户端进行保存(Cookie 或其它方法)。当该用户再次访问该网站时,ID 会被送回服务器(通过 Cookie 或其它手段)。服务器得到用户的 ID 后,即可从保存的 Session 信息中查阅该用户(比如是否已登录等)。
以下内容的命令行,都在 git bash 界面下或者 Linux 系统的终端下。
mkdir my-web-app
cd my-web-app
$ npm init
回答相关问题后,会在项目文件夹下生成 package.json 文件,此文件用于描述项目、记录依赖的库、建立要执行的命令等。
$ git config user.name "用户名"
$ git config user.email "电子邮件地址"
$ git init
用下面命令建立 .gitignore 文件,并将所有处于 node_modules/ 路径下的文件全部排除在版本控制之外。
$ echo node_modules/ > .gitignore
安装 Express 框架,并将对其的依赖记录在 package.json 中(--save的作用)。
$ npm install --save express
在项目文件夹下建立 app.js 文件,并编写内容如下:
const express = require('express')
const app = express()
app.listen(3000, function(){
console.log("服务器已经启动,正监听端口 3000...")
})
$ node app
服务器已经启动,正监听端口 3000...
浏览 http://localhost:3000, 得到以下响应:
Cannot GET /
说明我们针对 / 的 get 请求没有得到处理。
Web 应用将特定地址和特定方法的请求交给不同的程序进行处理,叫做请求路由。
我们给应用添加路由,app.js 的同,内容现在如下:
const express = require('express')
const app = express()
app.get("/",function(req,res){
res.send("这是首页。")
})
app.get("/login",function(req,res){
res.send("这是登录页。")
})
app.post("/login",function(req,res){
res.send("在这里处理登录,根据情况跳转到不同的页面。")
})
app.get("/logout",function(req,res){
res.send("您已经成功退出。")
})
//当以上路由都不满足时,进入此路由
app.all("*",function(req,res){
//先设置响应码,再送回错误信息
res.status(404).send("错误 404,请求的资源不存在")
})
app.listen(3000,function(){
console.log("Server Started on port 3000...")
})
路由格式:
app.请求方法名(“请求地址”,处理请求的函数定义)
处理请求的函数中,第一个参数是请求对象,第二个参数是响应对象,在函数中均可读可写。
res.send()方法把响应内容送回客户端。
在浏览器地址栏中访问 http://localhost:3000/, 响应如下:
这是首页
在浏览器地址栏中访问 http://localhost:3000/login, 响应如下:
这是登录页
在 postman 中用 post 方法访问 http://localhost:3000/login, 响应如下:
在这里处理登录,根据情况跳转到不同的页面。
在浏览器地址栏中访问:http://localhost:3000/abcde,响应如下:
错误 404,请求的资源不存在!
说明我们的路由编写正确。
postman 是做 web 请求测试的工具,对于测试 web api 非常有用。
通过浏览器地址栏访问网站是 get 请求。post 请求可通过 html 表单的 method 方法来决定。
<form action="/login" method="post">
...
<input type="submit" value="提交" />
</form>
在 web 架构中,中间件(middleware)是指在请求得到响应前,对请求进行导引、解析、记录、验证、修改等操作的程序。
在 express 框架中,在对请求进行路由分发前,利用应用程序对象的use方法来插入中间件。express 的中间件其实就是这样一个函数:
function(req,res,next){...}
它的第一个参数是请求对象,第二参数是响应对象,第三个参数是一个函数,执行它后,可将请求向下继续传递,否则请求将会被拦截。
修改 app.js 文件,在 const app = express() 下面加入以下内容:
...
const app = express()
app.use(function(req,res,next){
console.log(`${new Date()} ${req.method} ${req.path}`)
next() //把请求往下传!
})
...
在浏览器访问:http://localhost:3000/, 查看控制台有以下输出:
Thu May 17 2018 20:44:35 GMT+0800 (CST) GET /
在浏览器访问:http://localhost:3000/login, 查看控制台有以下输出:
Thu May 17 2018 20:44:51 GMT+0800 (CST) GET /login
注意:您的输出因日期时间不同而与上面有所不同。
这说明我们的中间件编写成功了。
静态文件是那些需要直接传送给客户端的文件,如图像文件、样式文件、JavaScript 文件等。我们不需要为每个静态文件做出路由,只需要告诉请求去哪里找到那些文件罢了。
在 app.js 第 1 行,加入以下内容:
const path = require('path')
在我们自己写的中间件前面,加入:
//第一个参数 "/static" 可随意写
app.use("/static",express.static(path.join(__dirname,"public")))
path 是 NodeJs 内置的模块,用于处理文件系统路径。
path.join(参数 1,参数 2,...)函数把各参数内容用特定的路径分隔符连接起来,比如 Windows 下用 \ 符号,Linux 和 Mac OS 系统用 / 符号。此函数在不同的操作系统下会做出正确的路径表示。
__dirname 在 NodeJS 中表示当前文件所在的路径。
加入以上规则后,如果请求地址为: http://localhost:3000/static/test.txt,请求会去 项目文件夹下的 public 文件夹去找一个名为 test.txt 的文件。找到,直接送出,请求处理结束(不往下走),找不到继续往下走。如果一直没有符合的路由,则最终落入 404 那条路由。
静态文件可放在多个位置,用多条配置语句进行配置就可以了。如可在刚才添加的那一行下面再次添加:
app.use("/download",express.static(path.join(__dirname,"files")))
在项目文件夹下建立 public 文件夹, 并在其中建立一个文件,加入一些内容:
$ mkdir public
$ echo 内容来自静态文件 > ./public/test.txt
然后在浏览器地址栏访问:http://localhost:3000/static/test.txt,如果得到以下响应:
内容来自静态文件
说明我们加入的静态文件服务功能起作用了。
在 web 应用的编程模式中,MVC(模型-视图-控制器)是最常用的模式。为了实现 MVC,我们需要把路由中的请求处理函数移到控制器中。
在项目文件夹下建立 controllers 文件夹,并在其中建立两个文件,一个叫 home-controller.js,另一个叫 auth-controller.js。
// home-controller.js
function home(req,res){
res.send("这是首页")
}
exports = module.exports = {home}
// auth-controllers.js
function send_login_form(req,res){
res.send("这是登录页。")
}
function do_login(req,res){
res.send("在这里处理登录,根据情况跳转到不同的页面。")
}
function logout(req,res){
res.send("您已经成功退出。")
}
exports = module.exports = {send_login_form, do_login, logout}
将 app.js 改写如下:
const path = require('path')
const express = require('express')
const homeController = require("./controllers/home-controller")
const authComntroller = require("./controllers/auth-controller")
const app = express()
app.use("/static",express.static(path.join(__dirname,"public")))
app.use(function(req,res,next){
console.log(`${new Date()} ${req.method} ${req.path}`)
next()
})
app.get("/",homeController.home)
app.get("/login",authComntroller.send_login_form)
app.post("/login",authComntroller.do_login)
app.get("/logout",authComntroller.logout)
//当以上路由都不满足时,进入此路由
app.all("*",function(req,res){
//先设置响应码,再送回错误信息
res.status(404).send("错误 404,请求的资源不存在")
})
app.listen(3000,function(){
console.log("Server Started on port 3000...")
})
根据需求不同,控制器可直接送出数据到客户端,也可将数据组装到 HTML 页面上,再将页面送回客户端。
为了把数据“组装”到页面上,我们需要一种模板引擎,帮我们完成“组装”工作。
常见的 express 可用的模板引擎有 ejs、jade、mustache 等。
$ npm install --save mustache-express
修改 app.js 如下:
...
const express = require('express')
//加入以下一行
const mustacheExpress = require('mustache-express')
...
const app = express()
//加入以下内容
//建立 mustache 引擎,并将视图文件扩展名为注册为.html
app.engine('html', mustacheExpress());
app.set('view engine', 'html'); //使用 html 引擎
app.set('views', __dirname + '/views'); //指定视图文件位置
在项目文件夹下建立 views 文件夹,建立 home.html 文件,内容如下:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<meta http-equiv="X-UA-Compatible" content="ie=edge">
<title>{{title}}</title>
</head>
<body>
<h1>Hi, {{name}}!</h1>
</body>
</html>
在 views 文件夹中建立 auth 文件夹,并建立如下几个文件:
login.html
<!-- file: login.html -->
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<meta http-equiv="X-UA-Compatible" content="ie=edge">
<title>{{title}}</title>
</head>
<body>
<div style="width: 500px; margin: 100px auto">
<form action="/login" method="post">
用户名:<input type="text" name="username" placeholder="请输入用户名"/>
<br><br>
密码:<input type="password" name="password" placeholder="请输入密码"/>
<br><br><br>
<input type="submit" value="登录" />
</form>
</div>
</body>
</html>
home-controller.js
function home(req,res){
res.render("home",{title:"首页",name:"Billy"})
}
exports = module.exports = {home}
auth-controller.js
function send_login_form(req,res){
res.render("auth/login",{title:"登录"})
}
function do_login(req,res){
res.send("在这里处理登录,根据情况跳转到不同的页面。")
}
function logout(req,res){
res.send("您已经成功退出。")
}
exports = module.exports = {send_login_form, do_login, logout}
在浏览器地址栏浏览http://localhost:3000/login,以确保能够看到登录框,点击“登录”能够看到:
在这里处理登录,根据情况跳转到不同的页面。
在浏览器地址栏浏览http://localhost:3000/,确保看到:
Hi, Billy!
为了实现与客户端的交流,服务器端应当能够接收来自客户端的数据,这些数据叫做请求参数。来自客户端的请求参数大致有以下几种形式。
为了获得作为地址片断的参数,我们先增加一个新的路由,为简单起见,我们直接输出数据:
app.get("/greet/:name",function(req,res){
res.send(`Hello, ${req.params.name}!`)
})
用浏览器地址栏访问 http://localhost:3000/greet/Tracy,可得到以下响应:
Hello, Tracy!
可见,取地址片断作为请求参数,一是地址片断要以冒号(:)开头,其次,要在响应函数里用请求对象的 params 对象来取出,例如:req.params.name
。
为获得查询字串中的参数,我们也增加一个路由:
app.get("/filter", function(req,res){
res.send(`You are filtering by: name is ${req.query.name} and age is ${req.query.age}` )
})
在浏览器地址栏访问:http://localhost:3000/filter?name=Tina&age=21,可得到以下响应:
You are filtering by: name is Tina and age is 21
可见,对于形如:?name=Tina&age=21
这样的请求查询字串中的参数,我们用请求对象的 query 对象来取出。如:req.query.name
。
为了获得请求体中的数据,我们首先需要对请求体数据(字符串)进行解析的中间件。
首先让我们安装 body-parser 模块:
npm install --save body-parser
然后,在 app.js 中加入:
...
const bodyParser = require('body-parser')
...
const app = express()
// 解析 application/x-www-form-urlencoded 数据(表单数据)
app.use(bodyParser.urlencoded({ extended: false }))
// 解析 application/json 数据
app.use(bodyParser.json())
...
现在我们创建一个路由,用于测试获取请求体的数据:
app.post("/get-body-data", function(req,res){
res.send(`Username is ${req.body.username} and Password is ${req.body.password}`)
})
然后,我们用 postman 分别用 post 方法分别将 x-www-form-urlencoded 数据和 json 数据提交到 http://localhost:3000/get-body-data 查看响应是否如下:
Username is admin and Password is adminpass
如果是,那么我们获取请求体参数成功!
目前为止的 app.js 如下:
const path = require('path')
const express = require('express')
const mustacheExpress = require('mustache-express')
const bodyParser = require('body-parser')
const homeController = require("./controllers/home-controller")
const authComntroller = require("./controllers/auth-controller")
const app = express()
// 解析 application/x-www-form-urlencoded 数据(表单数据)
app.use(bodyParser.urlencoded({ extended: false }))
// 解析 application/json 数据
app.use(bodyParser.json())
//建立 mustache 引擎,并将视图文件扩展名为注册为.html
app.engine('html', mustacheExpress());
app.set('view engine', 'html'); //使用 html 引擎
app.set('views', __dirname + '/views'); //指定视图文件位置
app.use("/static",express.static(path.join(__dirname,"public")))
app.use(function(req,res,next){
console.log(`${new Date()} ${req.method} ${req.path}`)
next()
})
app.get("/",homeController.home)
app.get("/login",authComntroller.send_login_form)
app.post("/login",authComntroller.do_login)
app.get("/logout",authComntroller.logout)
//用 http://localhost:3000/greet/Billy 进行测试
app.get("/greet/:name",function(req,res){
res.send(`Hello, ${req.params.name}`)
})
//用 http://localhost:3000/filter?name=Tina&age=21 进行访问测试
app.get("/filter", function(req,res){
res.send(`You are filtering by: name is ${req.query.name} and age is ${req.query.age}` )
})
//用 postman 分别用 post 方法分别将 x-www-form-urlencoded 数据
//和 json 数据提交到 http://localhost:3000/get-body-data 进行测试
app.post("/get-body-data", function(req,res){
res.send(`Username is ${req.body.username} and Password is ${req.body.password}`)
})
//当以上路由都不满足时,进入此路由
app.all("*",function(req,res){
//先设置响应码,再送回错误信息
res.status(404).send("错误 404,请求的资源不存在")
})
app.listen(3000,function(){
console.log("Server Started on port 3000...")
})