Skip to content

Files

Latest commit

72b80aa · Jan 9, 2022

History

History
405 lines (276 loc) · 12.4 KB

05.md

File metadata and controls

405 lines (276 loc) · 12.4 KB

五、编写 Web 应用

用 Node 编写的最常见的应用类型是 web 应用。我们已经在第 2 章中看到了如何编写一个简单的 HTTP 服务器。在本章中,我们将更深入地使用 Express.js 框架来了解如何构建真正的 web 应用。

在第 2 章中,我们看到了如何编写一个真正基本的 web 服务器:

代码清单 32

  const http
  = require('http')
  const
  server = http.createServer((request, response) => {

  response.writeHead(200, {'Content-Type': 'text/html'}) 
    response.end('<h1>Hello from Node.js</h1>')
  })
  server.listen(8000)

这段代码创建了一个监听端口 8000 的 HTTP 服务器。无论什么请求到达服务器,响应总是<h1>Hello from Node.js</h1>。我们在这里使用end方法,因为我们只有一个字符串要发送给客户端。一般来说,我们可以多次使用response.write,并且只能在最后调用response.end()来关闭连接。

request对象包含关于请求的所有信息,因此我们可以使用它来决定我们必须向客户端发送什么样的响应。

response对象用于构建响应。我们用它来添加状态代码(200)和Content-Type标题。

如果我们想构建一些不那么琐碎的东西,我们可以检查request.url来决定将哪些内容发送给客户端。

例如,我们可以这样写,根据请求的网址发送不同的响应:

代码清单 33

  const http = require('http')
  const server =
  http.createServer((request, response) => {
    response.writeHead(200,
  {'Content-Type': 'text/html'})
    if (request.url === '/about') {

  response.write('<h1>About Node.js</h1>')
    } else {
      response.write('<h1>Hello
  from Node.js/h1>')
    }
    response.end();
  })
  server.listen(8000)

request对象有很多其他属性和方法来分析请求。例如,attribute方法包含请求的类型(GET、POST、PUT、…)。

http模块非常容易使用,加上 HTTP 协议也很容易,我们可以考虑直接使用http模块编写自己的 web 应用。但是,即使很容易,Node 社区已经创建了许多库和框架来加快 web 应用的开发。第一个也是最著名的是 Express.js

Express.js 是一个最小的 web 框架,它建立在组成中间件的思想上(这个思想来自带有 Rack 的 Ruby 世界)。即使 Express 非常小,Node 社区也已经构建了大量插件来为 Express 添加功能,这样我们就可以在应用中准确地选择我们需要的东西。

最小的 Express 应用是这样的:

代码清单 34

  const
  express = require('express')
  const app =
  express()

  app.get('/',
  (req, res) => {

  res.send('Hello World!')
  });

  app.listen(8000,
  () = > {

  console.log('Example app listening on port 8000!');
  });

在运行这段代码之前,我们需要使用npm在我们的node_modules文件夹中安装 Express:

代码清单 35

  > npm
  install express

代码需要express模块,这是一个用于创建应用的函数。通过应用实例,我们可以定义调用路由时要使用的路由和回调。

在前面的例子中,我们已经定义了一个响应 HTTP GET / (root)的单一路由,我们使用字符串Hello World!进行响应

这个例子和之前使用了 raw http模块的例子没有太大的区别,但是结构稍微有点不同,更容易维护和扩展。

从这里我们可以使用 HTTP 方法getpostput等添加更多路由。,并指定路由必须匹配的模式。

例如,一个基本的 CRUD API 可能是这样的:

代码清单 36

  const express = require('express')
  const app = express()

  app.get('/users', (req, res) =>
  {
    // get all users
  })

  app.get('/users/:id', (req, res)
  => {
    // get the user with the
  specified id
  })

  app.post('/users', (req, res)
  => {
    // create a new user
  })

  app.put('/users/:id', (req, res)
  => {
    // update the user with
  specified id
  })

  app.delete('/users/:id', (req,
  res) => {
    // delete the user with specified
  id
  })

  app.listen(8000, () = > {
    console.log('Example app
  listening on port 8000!');
  })

| | 注意:CRUD 代表创建、读取、更新和删除。它们是对数据库的四种基本操作,或者更一般地说,是对资源的四种基本操作。 |

在一个app对象上,我们附加了指定所用方法的各种路线。这个应用编程接口在假设的用户资源上工作,并公开一组端点来操纵用户。请注意访问单个用户的getpost。在这些情况下,URL 将包含用户的id(类似于/users/42),并且在路线声明中,Express 使用:id(冒号 id)来指定该部分是可变的。在请求中,我们将在req.params.id中找到id的值。

为了构建应用编程接口响应,我们可以使用回调中可用的res对象。例如,要返回用户列表,我们可以简单地使用方法send将用户数组发送给客户端:

代码清单 37

  const
  express = require('express')
  const app =
  express()

  app.get('/users',
  (req, res) => {
    const
  users = [{id: 1, name: 'Emanuele'}, {id: 2, name: 'Tessa'}]
    res.send(users)
  })
  app.listen(8000, () = > {

  console.log('Example app listening on port 8000!');
  })

Express.js 将users数组视为一个对象,将其序列化为 JSON 数组(默认情况下),并设置所有必要的头和状态代码来组成正确的响应。

如果我们使用curl调用 API:

代码清单 38

  curl -i http://localhost:8000/users

答案是:

代码清单 39

  HTTP/1.1
  200 OK
  X-Powered-By:
  Express
  Content-Type:
  application/json; charset=utf-8
  Content-Length:
  52
  ETag:
  W/"34-TWdsT6/ZO3egRrB46eWd+w"
  Date: Tue,
  23 Feb 2016 16:23:37 GMT
  Connection:
  keep-alive

  [{"id":1,"name":"emanuele"},{"id":2,"name":"Tessa"}]

| | 提示:即使 res.send 将对象转换为 JSON 格式,response 也有一个 JSON 方法,该方法将内容类型显式指定为 application/json。 |

有时我们需要配置响应,例如,指定特定的状态代码或特定的头。res对象有一系列方法可以做到这一点。

如果我们需要指定一个特定的状态代码,我们可以使用res.status(code),其中code是我们想要的实际状态代码。默认情况下是 200。

sendjson方法有一个重载来指定状态代码:

代码清单 40

  res.json(200,
  users) 
  res.send(200, users)

要添加标题,我们使用方法res.set(headerName, headerValue)

对于某些标头,存在一种特殊方法。例如,创建资源的应用编程接口在location头中返回新创建的资源的网址是一个很好的做法:

代码清单 41

  app.post('/users',
  (req, res) => {
    // create
  a new user
    const user = createUser(req.body)
    res.location(`/users/${user.id}`)
    res.status(201)
  })

这个例子展示了一个使用createUser函数创建新用户的帖子的可能实现。然后我们添加位置头,并将状态代码 201(已创建)发送给客户端。在这种情况下,身体是空的。

到目前为止,我们已经看到了如何使用 Express.js 创建一个简单的 API,但是 Express 不仅仅是关于 API 的,它允许我们使用模板引擎创建带有 HTML 页面的完整 web 应用。

如果我们需要使用服务器端模板引擎呈现一个 HTML 页面,那么 Jade 是 Express.js 的众多可用选项之一

要开始使用 Jade,我们首先需要安装它:

代码清单 42

  npm install jade

然后,我们需要在应用对象上设置几个选项:

代码清单 43

  app.set('view engine', 'jade')
  app.set('views', './views')

第一个set指定我们要用的视图引擎是 Jade。第二个告诉 app,视图文件将在当前项目的./views文件夹中。

完成这两项设置后,我们准备创建第一个模板,并定义一个路由器来呈现它。

创建模板相当容易;Jade 的语法非常简单,很容易学习。例如,我们可以在./views文件夹中创建新文件hello.jade:

代码清单 44

  html
    head
      title=
  title
    body
      h1=
  messageTitle
      div(class='container')
        = messageText

这是一个 Jade 模板,可编译为:

代码清单 45

  <html>
    <head>
     <title>Hey</title>
    </head>
    <body>
      <h1>Hello noders</h1>
      <div class="container">
        lorem ipsum....
      </div>
    </body>
  </html>

编译过程由 Express with Jade 管理,以响应对所选路由的调用。在我们的例子中,我们可以定义一条路线:

代码清单 46

  app.get('/hello', function (req,
  res) {
    res.render('hello', {
      title: 'Hey', 
      messageTitle: 'Hello noders', 
      messageText: 'lorem ipsum....'
    })
  })

该路径响应/hello并呈现名为hello的视图(与文件名匹配)。render方法接收我们想要传递给视图的对象。该对象由三个属性组成:titlemessageTitlemessageText

所以从浏览器调用路线/hello会呈现一个 HTML 页面,如下所示:

图 11:快递中的你好世界

Jade 是一种全模板语言,支持构建 HTML 页面所需的所有语义。我们可以使用条件句、服务器端代码和变量。好的一点是你不需要关闭标签,所以 Jade 文件相当紧凑。

如前所述,Express 是一个最小框架,基于中间件的思想。中间件是对象的请求和响应通过的一堆函数。每个中间件可以对请求和响应做任何它需要做的事情,并将它传递给下一个中间件。这是一种责任链。

| | 注意:责任链是一种设计模式,旨在对一个对象创建一系列操作。链的每个元素都对输入对象做一些事情,并将结果传递给链的下一个元素。 |

有了这个强大的想法,我们可以用我们需要的中间件来构建堆栈。例如,如果我们需要管理一个 cookie 来识别用户,我们可以使用一个检查 cookie 的中间件,如果它无效,那么我们可以用一个向客户端返回 HTTP 401 状态的短路来断开这个链。

写中间件很容易;它只是一个具有三个参数的函数:请求、响应和下一个要调用的中间件:

代码清单 47

  function myMiddleware(req, res,
  next) {
    // do what you need and if everything is ok
    next()
  }
  app.use(myMiddleware)

我们能用中间件做什么?

中间件功能可以执行一些代码,或者对请求或响应对象进行更改。最后,他们可以结束请求-响应周期或调用下一个函数来继续中间件链。

例如,使用中间件,我们可以编写一个记录器:

代码清单 48

  function
  requestLogger(req, res, next) {

  console.log(`${req.method} ${req.originalUrl}`)
    next()
  }
  app.use(requestLogger)

这个简单的中间件功能将请求的方法和网址记录到控制台。

功能中间件的思想非常强大,其易用性是 Express.js 成功的原因之一。

大多数 Express.js 插件都是作为插件实现的,我们可以在npmjs.com上找到很多中间件来满足每个应用的需求,从安全性到日志记录。

可以使用app.use功能为整个应用设置中间件,或者直接在路由定义上通过该功能的每条路由设置中间件:

代码清单 49

  function checkPermissionMiddleware(req,
  res, next){
    // verify permissions. If ok call next()
    if (ok){
      next()
    } else {
      res.status(401)
    }
  }

  app.post('/users', checkPermissionMiddleware, (req, res) => {
    // create a new user
    const user = createUser(req.body)
    res.location(`/users/${user.id}`)
    res.status(201)
  })

在这种配置下,checkPermissionMiddleware功能将在每次post请求到路线/users之前被调用。它可以检查req对象中是否有正确的授权头来管理继续请求。如果它们存在,它将调用下一个中间件(实际上是在/users路线上定义的函数),否则它将返回一个 401,甚至不调用后续的中间件。

中间件模块化的简单性给了我们很大的灵活性,增强了安全性,并且不影响性能。在最后这个例子中,如果用户没有访问资源的权限,那么危险的代码甚至不会被执行。