[关闭]
@artman328 2019-05-28T02:30:42.000000Z 字数 10862 阅读 1231

利用 NodeJs 和 Express 快速搭建 Web 应用

node express web


本文地址:https://zybuluo.com/artman328/note/1151185

第一部分 涉及的相关知识要点

一、Web 应用的结构

Web 应用由两部分组成:一是用户用于交互的用户代理(User agent),通常是浏览器;二是用于提供信息服务的 web 服务器。双方通过 HTTP 协议进行 请求/响应 模式的通信。

Created with Raphaël 2.1.2浏览器浏览器服务器服务器-- HTTP 请求(GET/POST/HEAD/PUT/DELETE)--服务器端客户端HTTP 响应

二、HTTP请求与响应

HTTP/1.1 (版本 1.1)的请求和响应都是特定格式的字符串。

1、请求

样例:

  1. POST /cgi-bin/process.cgi HTTP/1.1
  2. User-Agent: Mozilla/4.0 (compatible; MSIE5.01; Windows NT)
  3. Host: www.tutorialspoint.com
  4. Content-Type: application/x-www-form-urlencoded
  5. Content-Length: 49
  6. Accept-Language: en-us
  7. Accept-Encoding: gzip, deflate
  8. Connection: Keep-Alive
  9. licenseID=string&content=string&/paramsXML=string

第 1 行为请求行,包含了请求方法(POST),请求的资源相对地址:/cgi-bin/process.cgi,以及请求所用的协议及其版本号:HTTP/1.1;

第 2 ~ 8 行为请求头,第行说明不同的内容;
第 9 行为一个空行,是请求头和请求体的分界线(有的请求没有请求体,如 GET 请求);
第 10 行为请求体。(请求体内容可以很长,头部:Content-Length 说明了请求体的字节数)。

2、响应

样例:

  1. HTTP/1.1 200 OK
  2. Date: Mon, 27 Jul 2009 12:28:53 GMT
  3. Server: Apache/2.2.14 (Win32)
  4. Last-Modified: Wed, 22 Jul 2009 19:15:56 GMT
  5. Content-Length: 88
  6. Content-Type: text/html
  7. Connection: Closed
  8. <html>
  9. <body>
  10. <h1>Hello, World!</h1>
  11. </body>
  12. </html>

第 1 行为响应行,说明了协议及其版本号:HTTP/1.1,响应状态编码:200,响应结果信息:OK;
第 2 ~ 7 行为响应头;
第 8 行为空行,作为响应头与响应体的分界;
第 9 行开始为响应体。(字节数由响应头 Content-Length 决定)

三、Cookie 和 Session

HTTP 协议是无状态协议,如果要记住访问服务器的用户,则可采用 Session 和 Cookie 技术。

1、Cookie

Cookie 是在客户端保存用户信息的方法,它要保存的信息由响应头带回来,并保存在用户的计算机中。浏览器在访问某个网址时,会将这个网址保存在本地的 Cookie 信息(如果有的话)带给服务器。这样服务器就可通过读取 Cookie 信息了解该用户了。

Created with Raphaël 2.1.2浏览器浏览器服务器服务器HTTP 请求(第一次请求,无 Cookie)服务器端客户端HTTP 响应(服务器设置 Cookie,保存到客户端)HTTP 请求(带上保存的 Cookie)查阅用户送来的Cookie 信息

2、Session

与 Cookie 不同,Session 是将用户信息保存到服务器的技术。服务器保存了用户信息后,会分配给该用户一个 ID ,并将该 ID 传回客户端进行保存(Cookie 或其它方法)。当该用户再次访问该网站时,ID 会被送回服务器(通过 Cookie 或其它手段)。服务器得到用户的 ID 后,即可从保存的 Session 信息中查阅该用户(比如是否已登录等)。

Created with Raphaël 2.1.2浏览器浏览器服务器服务器HTTP 请求(第一次请求,无 Session ID)服务器端客户端HTTP 响应(服务器建立用户Session,保存信息,把 Session ID 传回客户端)HTTP 请求(带上保存的 Session ID)根据 Session ID,查阅保存在服务器端 Session 中的用户信息

第二部分 搭建 Web 服务器

以下内容的命令行,都在 git bash 界面下或者 Linux 系统的终端下。

一、建立项目

1、建立项目文件夹

  1. mkdir my-web-app
  2. cd my-web-app

2、初始化 NodeJS 项目

  1. $ npm init

回答相关问题后,会在项目文件夹下生成 package.json 文件,此文件用于描述项目、记录依赖的库、建立要执行的命令等。

3、配置 git 并初始化 git 仓库

  1. $ git config user.name "用户名"
  2. $ git config user.email "电子邮件地址"
  3. $ git init

4、编写.gitignore 文件

用下面命令建立 .gitignore 文件,并将所有处于 node_modules/ 路径下的文件全部排除在版本控制之外。

  1. $ echo node_modules/ > .gitignore

二、编写最基本的 Web 应用

1、安装 Express 框架

安装 Express 框架,并将对其的依赖记录在 package.json 中(--save的作用)。

  1. $ npm install --save express

2、编写 Web 应用

在项目文件夹下建立 app.js 文件,并编写内容如下:

  1. const express = require('express')
  2. const app = express()
  3. app.listen(3000, function(){
  4. console.log("服务器已经启动,正监听端口 3000...")
  5. })

4、启动应用

  1. $ node app
  2. 服务器已经启动,正监听端口 3000...

浏览 http://localhost:3000, 得到以下响应:
Cannot GET /

说明我们针对 / 的 get 请求没有得到处理。

三、给应用添加处理路由

Web 应用将特定地址和特定方法的请求交给不同的程序进行处理,叫做请求路由。

1、添加路由

我们给应用添加路由,app.js 的同,内容现在如下:

  1. const express = require('express')
  2. const app = express()
  3. app.get("/",function(req,res){
  4. res.send("这是首页。")
  5. })
  6. app.get("/login",function(req,res){
  7. res.send("这是登录页。")
  8. })
  9. app.post("/login",function(req,res){
  10. res.send("在这里处理登录,根据情况跳转到不同的页面。")
  11. })
  12. app.get("/logout",function(req,res){
  13. res.send("您已经成功退出。")
  14. })
  15. //当以上路由都不满足时,进入此路由
  16. app.all("*",function(req,res){
  17. //先设置响应码,再送回错误信息
  18. res.status(404).send("错误 404,请求的资源不存在")
  19. })
  20. app.listen(3000,function(){
  21. console.log("Server Started on port 3000...")
  22. })
路由格式:
app.请求方法名(“请求地址”,处理请求的函数定义)
处理请求的函数中,第一个参数是请求对象,第二个参数是响应对象,在函数中均可读可写。
res.send()方法把响应内容送回客户端。

2、测试路由

在浏览器地址栏中访问 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 方法来决定。
  1. <form action="/login" method="post">
  2. ...
  3. <input type="submit" value="提交" />
  4. </form>

四、编写中间件

在 web 架构中,中间件(middleware)是指在请求得到响应前,对请求进行导引、解析、记录、验证、修改等操作的程序。
在 express 框架中,在对请求进行路由分发前,利用应用程序对象的use方法来插入中间件。express 的中间件其实就是这样一个函数:
function(req,res,next){...}
它的第一个参数是请求对象,第二参数是响应对象,第三个参数是一个函数,执行它后,可将请求向下继续传递,否则请求将会被拦截。

1、编写日志中间件

修改 app.js 文件,在 const app = express() 下面加入以下内容:

  1. ...
  2. const app = express()
  3. app.use(function(req,res,next){
  4. console.log(`${new Date()} ${req.method} ${req.path}`)
  5. next() //把请求往下传!
  6. })
  7. ...

2、测试日志中间件

在浏览器访问:http://localhost:3000/, 查看控制台有以下输出:

  1. Thu May 17 2018 20:44:35 GMT+0800 (CST) GET /

在浏览器访问:http://localhost:3000/login, 查看控制台有以下输出:

  1. Thu May 17 2018 20:44:51 GMT+0800 (CST) GET /login
注意:您的输出因日期时间不同而与上面有所不同。    

这说明我们的中间件编写成功了。

五、提供静态文件服务

静态文件是那些需要直接传送给客户端的文件,如图像文件、样式文件、JavaScript 文件等。我们不需要为每个静态文件做出路由,只需要告诉请求去哪里找到那些文件罢了。

1、指示访问静态文件的规则

在 app.js 第 1 行,加入以下内容:
const path = require('path')

在我们自己写的中间件前面,加入:

  1. //第一个参数 "/static" 可随意写
  2. 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")))

2、测试静态文件访问

在项目文件夹下建立 public 文件夹, 并在其中建立一个文件,加入一些内容:

  1. $ mkdir public
  2. $ echo 内容来自静态文件 > ./public/test.txt

然后在浏览器地址栏访问:http://localhost:3000/static/test.txt,如果得到以下响应:
内容来自静态文件
说明我们加入的静态文件服务功能起作用了。

六、将请求处理函数移到控制器

在 web 应用的编程模式中,MVC(模型-视图-控制器)是最常用的模式。为了实现 MVC,我们需要把路由中的请求处理函数移到控制器中。

1、编写控制器

在项目文件夹下建立 controllers 文件夹,并在其中建立两个文件,一个叫 home-controller.js,另一个叫 auth-controller.js。

  1. // home-controller.js
  2. function home(req,res){
  3. res.send("这是首页")
  4. }
  5. exports = module.exports = {home}
  1. // auth-controllers.js
  2. function send_login_form(req,res){
  3. res.send("这是登录页。")
  4. }
  5. function do_login(req,res){
  6. res.send("在这里处理登录,根据情况跳转到不同的页面。")
  7. }
  8. function logout(req,res){
  9. res.send("您已经成功退出。")
  10. }
  11. exports = module.exports = {send_login_form, do_login, logout}

2、改写路由

将 app.js 改写如下:

  1. const path = require('path')
  2. const express = require('express')
  3. const homeController = require("./controllers/home-controller")
  4. const authComntroller = require("./controllers/auth-controller")
  5. const app = express()
  6. app.use("/static",express.static(path.join(__dirname,"public")))
  7. app.use(function(req,res,next){
  8. console.log(`${new Date()} ${req.method} ${req.path}`)
  9. next()
  10. })
  11. app.get("/",homeController.home)
  12. app.get("/login",authComntroller.send_login_form)
  13. app.post("/login",authComntroller.do_login)
  14. app.get("/logout",authComntroller.logout)
  15. //当以上路由都不满足时,进入此路由
  16. app.all("*",function(req,res){
  17. //先设置响应码,再送回错误信息
  18. res.status(404).send("错误 404,请求的资源不存在")
  19. })
  20. app.listen(3000,function(){
  21. console.log("Server Started on port 3000...")
  22. })

七、送出响应 HTML 页面

根据需求不同,控制器可直接送出数据到客户端,也可将数据组装到 HTML 页面上,再将页面送回客户端。
为了把数据“组装”到页面上,我们需要一种模板引擎,帮我们完成“组装”工作。
常见的 express 可用的模板引擎有 ejs、jade、mustache 等。

1、安装 Mustache 模板引擎

  1. $ npm install --save mustache-express

2、加载并配置模板引擎

修改 app.js 如下:

  1. ...
  2. const express = require('express')
  3. //加入以下一行
  4. const mustacheExpress = require('mustache-express')
  5. ...
  6. const app = express()
  7. //加入以下内容
  8. //建立 mustache 引擎,并将视图文件扩展名为注册为.html
  9. app.engine('html', mustacheExpress());
  10. app.set('view engine', 'html'); //使用 html 引擎
  11. app.set('views', __dirname + '/views'); //指定视图文件位置

3、创建 views 文件夹及相应的视图文件

在项目文件夹下建立 views 文件夹,建立 home.html 文件,内容如下:

  1. <!DOCTYPE html>
  2. <html lang="en">
  3. <head>
  4. <meta charset="UTF-8">
  5. <meta name="viewport" content="width=device-width, initial-scale=1.0">
  6. <meta http-equiv="X-UA-Compatible" content="ie=edge">
  7. <title>{{title}}</title>
  8. </head>
  9. <body>
  10. <h1>Hi, {{name}}!</h1>
  11. </body>
  12. </html>

在 views 文件夹中建立 auth 文件夹,并建立如下几个文件:
login.html

  1. <!-- file: login.html -->
  2. <!DOCTYPE html>
  3. <html lang="en">
  4. <head>
  5. <meta charset="UTF-8">
  6. <meta name="viewport" content="width=device-width, initial-scale=1.0">
  7. <meta http-equiv="X-UA-Compatible" content="ie=edge">
  8. <title>{{title}}</title>
  9. </head>
  10. <body>
  11. <div style="width: 500px; margin: 100px auto">
  12. <form action="/login" method="post">
  13. 用户名:<input type="text" name="username" placeholder="请输入用户名"/>
  14. <br><br>
  15. 密码:<input type="password" name="password" placeholder="请输入密码"/>
  16. <br><br><br>
  17. <input type="submit" value="登录" />
  18. </form>
  19. </div>
  20. </body>
  21. </html>

4、修改控制器

home-controller.js

  1. function home(req,res){
  2. res.render("home",{title:"首页",name:"Billy"})
  3. }
  4. exports = module.exports = {home}

auth-controller.js

  1. function send_login_form(req,res){
  2. res.render("auth/login",{title:"登录"})
  3. }
  4. function do_login(req,res){
  5. res.send("在这里处理登录,根据情况跳转到不同的页面。")
  6. }
  7. function logout(req,res){
  8. res.send("您已经成功退出。")
  9. }
  10. exports = module.exports = {send_login_form, do_login, logout}

5、测试路由及页面

在浏览器地址栏浏览http://localhost:3000/login,以确保能够看到登录框,点击“登录”能够看到:
在这里处理登录,根据情况跳转到不同的页面。

在浏览器地址栏浏览http://localhost:3000/,确保看到:
Hi, Billy!

八、获取请求参数

为了实现与客户端的交流,服务器端应当能够接收来自客户端的数据,这些数据叫做请求参数。来自客户端的请求参数大致有以下几种形式。

1、地址片断作为请求参数

为了获得作为地址片断的参数,我们先增加一个新的路由,为简单起见,我们直接输出数据:

  1. app.get("/greet/:name",function(req,res){
  2. res.send(`Hello, ${req.params.name}!`)
  3. })

用浏览器地址栏访问 http://localhost:3000/greet/Tracy,可得到以下响应:
Hello, Tracy!

可见,取地址片断作为请求参数,一是地址片断要以冒号(:)开头,其次,要在响应函数里用请求对象的 params 对象来取出,例如:req.params.name

2、地址栏查询字串中的请求参数

为获得查询字串中的参数,我们也增加一个路由:

  1. app.get("/filter", function(req,res){
  2. res.send(`You are filtering by: name is ${req.query.name} and age is ${req.query.age}` )
  3. })

在浏览器地址栏访问: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

3、请求体中的请求参数

为了获得请求体中的数据,我们首先需要对请求体数据(字符串)进行解析的中间件。
首先让我们安装 body-parser 模块:

  1. npm install --save body-parser

然后,在 app.js 中加入:

  1. ...
  2. const bodyParser = require('body-parser')
  3. ...
  4. const app = express()
  5. // 解析 application/x-www-form-urlencoded 数据(表单数据)
  6. app.use(bodyParser.urlencoded({ extended: false }))
  7. // 解析 application/json 数据
  8. app.use(bodyParser.json())
  9. ...

现在我们创建一个路由,用于测试获取请求体的数据:

  1. app.post("/get-body-data", function(req,res){
  2. res.send(`Username is ${req.body.username} and Password is ${req.body.password}`)
  3. })

然后,我们用 postman 分别用 post 方法分别将 x-www-form-urlencoded 数据和 json 数据提交到 http://localhost:3000/get-body-data 查看响应是否如下:
Username is admin and Password is adminpass

如果是,那么我们获取请求体参数成功!

目前为止的 app.js 如下:

  1. const path = require('path')
  2. const express = require('express')
  3. const mustacheExpress = require('mustache-express')
  4. const bodyParser = require('body-parser')
  5. const homeController = require("./controllers/home-controller")
  6. const authComntroller = require("./controllers/auth-controller")
  7. const app = express()
  8. // 解析 application/x-www-form-urlencoded 数据(表单数据)
  9. app.use(bodyParser.urlencoded({ extended: false }))
  10. // 解析 application/json 数据
  11. app.use(bodyParser.json())
  12. //建立 mustache 引擎,并将视图文件扩展名为注册为.html
  13. app.engine('html', mustacheExpress());
  14. app.set('view engine', 'html'); //使用 html 引擎
  15. app.set('views', __dirname + '/views'); //指定视图文件位置
  16. app.use("/static",express.static(path.join(__dirname,"public")))
  17. app.use(function(req,res,next){
  18. console.log(`${new Date()} ${req.method} ${req.path}`)
  19. next()
  20. })
  21. app.get("/",homeController.home)
  22. app.get("/login",authComntroller.send_login_form)
  23. app.post("/login",authComntroller.do_login)
  24. app.get("/logout",authComntroller.logout)
  25. //用 http://localhost:3000/greet/Billy 进行测试
  26. app.get("/greet/:name",function(req,res){
  27. res.send(`Hello, ${req.params.name}`)
  28. })
  29. //用 http://localhost:3000/filter?name=Tina&age=21 进行访问测试
  30. app.get("/filter", function(req,res){
  31. res.send(`You are filtering by: name is ${req.query.name} and age is ${req.query.age}` )
  32. })
  33. //用 postman 分别用 post 方法分别将 x-www-form-urlencoded 数据
  34. //和 json 数据提交到 http://localhost:3000/get-body-data 进行测试
  35. app.post("/get-body-data", function(req,res){
  36. res.send(`Username is ${req.body.username} and Password is ${req.body.password}`)
  37. })
  38. //当以上路由都不满足时,进入此路由
  39. app.all("*",function(req,res){
  40. //先设置响应码,再送回错误信息
  41. res.status(404).send("错误 404,请求的资源不存在")
  42. })
  43. app.listen(3000,function(){
  44. console.log("Server Started on port 3000...")
  45. })
添加新批注
在作者公开此批注前,只有你和作者可见。
回复批注