Node.js<九>——掌握http模块并开发服务器

993 阅读9分钟

Web服务器

什么是Web服务器?

  • 当应用程序(客户端)需要某一个资源时,可以向一台服务器,通过Http请求获取到这个资源;提供资源的这个服务器,就是一个Web服务器

目前有很多开源的Web服务器:NginxApache(静态)、Apache Tomcat(静态、动态)、Node.js

Web服务器初体验

  1. 首先需要引入http模块
  2. 该模块有一个createServer方法,可以帮助我们创建一个web服务器,其需要传入一个回调函数,该回调函数会在客户端与服务器建立连接之后被执行
  3. 刚刚我们只是创建出了服务器,但是还没有去启动它。createServer方法会返回一个实例,该实例上面有一个listen方法可以监听指定的端口号和主机,我们可以给该方法传入服务器成功启动之后执行的函数,该函数在执行的时候会被传入两个参数:
  • reqrequest请求参数,包含请求相关的信息
  • resresponse响应对象,包含我们要响应给客户端的信息
  1. 如果我们更改了项目代码,那么必须要重新使用node重启一下服务器,要不然客户端接受到的数据就不是最新的了。所以为了我们开发的方便,可以提前全局安装一下nodemon这个库,它可以帮助我们在保存之后自动重新执行某个文件
const http = require('http')

// 创建一个web服务器
const serve = http.createServer((req, res) => {
  // 其实这个res继承了我们之前学的Scream.Writable类,所以其也可以使用end方法
  res.end('Hello World!')
})

// 启动服务器,并且指定端口号和主机
serve.listen(3000, '127.0.0.1', () => {
  console.log('服务器成功启动!');
})

因为res.end方法会先将数据写入后再关闭,所以客户端是可以接受到服务器返回的数据的

创建服务器的方式

  1. http.createServer可以返回给我们一个服务器对象,通过该对象上的listen属性就可以监听对应的端口号,同时我们也可以在主机上监听多个端口号
const http = require('http')

// 创建一个web服务器
const serve1 = http.createServer((req, res) => {
  res.end('Hello serve1!')
})

// 启动服务器,并且指定端口号和主机
serve1.listen(3000, '0.0.0.0', () => {
  console.log('服务器1成功启动!');
})

// 创建一个web服务器
const serve2 = http.createServer((req, res) => {
  res.end('Hello serve2!')
})

// 启动服务器,并且指定端口号和主机
serve2.listen(3001, '0.0.0.0', () => {
  console.log('服务器2成功启动!');
})
  1. http模块里面有一个Server类,通过new http.Server也可以去创建一个服务器,同样是用返回的服务器对象中的listen方法去启动并监听端口号
const http = require('http')

// 创建一个web服务器
const serve2 = new http.Server((req, res) => {
  res.end('Hello serve2!')
})

// 启动服务器,并且指定端口号和主机
serve2.listen(3001, '127.0.0.1', () => {
  console.log('服务器2成功启动!');
})

其实这两种方法的本质上是一样的,在源码中,createServer其实就是包裹了一层new Serve而已

监听方法的使用

  • Server通过listen方法来开启服务器,并且在某一个主机和端口上监听网络请求

    • 也就是当通过ip:port的方式发送到我们监听的Web服务器端口上时,我们就可以对其进行相关的处理
  • listen函数有三个参数

    • 端口号port:可以不传,系统会默认分配端口号,这个随机分配的端口号可以在服务器对象上的address方法返回的port属性中查找到,后续项目中我们会写入到环境变量中
// 启动服务器,并且指定端口号和主机
serve2.listen(() => {
  console.log('服务器2成功启动!');
  console.log(serve2.address().port); // 57980
})
  • 127.0.0.1:回环地址,表达的意思其实是我们主机自己发出去的包,直接被自己接收

  • 正常的数据包是要经过 应用层 -> 传输层 -> 网络层 -> 数据链路层 -> 物理层

  • 而环回地址,是在网络层直接就被获取到了,是不会经过数据链路层和物理层的

    • 所以我们监听127.0.0.1时,在同一个网段下的主机中,通过ip地址是不能访问的(包括自己的主机都不能用ip地址进行访问)

    • 0.0.0.0(默认值):

      • 监听IPv4上的所有地址,这就包括了127.0.0.1,再根据端口号找到不同的应用程序
      • 所以我们监听0.0.0.0时,在同一个网段下的主机中,通过ip地址是可以访问的
  • 回调函数:服务器启动成功时的回调函数

request对象

request.url

  1. 我们可以通过req.url获取到客户端传递过来的url
  2. URL是一个全局类,其接受两个参数,第一个是要解析的字符串,使用方法new URL(input[, base])
  • input:要解析的绝对或相对的输入网址。 如果 input 是相对的,则需要 base。 如果 input 是绝对的,则忽略 base。 如果 input 不是字符串,则先转换成字符串
  • base:如果 input 不是绝对的,则为要解析的基本网址。 如果 base 不是字符串,则先转换成字符串
  • 最终该函数会返回一个对象,里面包含了url参数对应的一些信息,包括pathnamesearchParams等等
  1. searchParams是一个特殊的对象,里面包含了我们url中的参数信息,但我们需要通过其get属性去获取
const http = require('http')

// 创建一个web服务器
const serve1 = http.createServer((req, res) => {
  const { pathname, searchParams } = new URL(req.url, 'http://localhost:3000')
  console.log(pathname);
  const username = searchParams.get('username')
  const pwd = searchParams.get('pwd')
  console.log(username, pwd);
  res.end(`${username} ${pwd}`)
})

// 启动服务器,并且指定端口号和主机
serve1.listen(3000, '127.0.0.1', () => {
  console.log('服务器1成功启动!');
})

request.method

  1. 我们可以通过req.method获取到客户端传递过来的请求方法
  2. 请求体中的数据是通过流的形式传递到服务器的。req本身是一个Stream的实例,所以其身上是有on方法去监听请求体中的数据有没有读取完全的,但读取到的数据是一个二进制的buffer对象
  3. req对象上面有一个setEncoding方法,设置了它之后,我们在后面读取到的数据就会自动按照指定的编码进行解析,如果我们设置了utf-8编码的话,读取到的就是一个我们能够识别的数据了
  4. 如果读取到的是一个json字符串,我们想要获取到里面的数据的话,就需要先利用JSON.parse方法将其转换成一个js对象,然后才能够拿到用户传递给我们的对应的参数值
const http = require('http')

// 创建一个web服务器
const serve1 = http.createServer((req, res) => {
  if (req.method === 'POST') {
    req.setEncoding('utf-8')
    req.on('data', data => {
      const { username, pwd } = JSON.parse(data)
      console.log(username, pwd);
    })
  }
})

// 启动服务器,并且指定端口号和主机
serve1.listen(3000, '127.0.0.1', () => {
  console.log('服务器1成功启动!');
})

Restful规范(设计风格)中,我们对于数据的增删查改应该通过不同的请求方式

  • GET:查询数据
  • POST:新建数据
  • PATCH:更新部分数据
  • PUT:更新整条数据
  • DELETE:删除数据

所以,我们可以通过判断不同的请求方式进行不同的处理

  • 比如创建一个用户
  • 请求接口为/users
  • 请求方式为POST请求
  • 携带数据usernamepassword

request.headers

request对象的header中也包含很多有用的信息,客户端会默认传递过来一些信息

  1. content-type是这次请求携带数据的类型:
  • application/json表示是一个json类型
  • text/plain表示是文本类型
  • application/xml表示是xml类型
  • multipart/form-data表示是上传文件
  1. content-length:文件的大小和长度,因为数据是以流的形式进行传递的,在服务端是用req.on('data',(data) => {})的形式读取数据,但每次读取的数据长度有限,我们需要根据数据长度来判断数据有没有读取完全
  2. keep-alive
  • http是基于TCP协议的,通常在进行一次请求和响应结果后会立即中断,下次再进行通信的时候需要重新建立连接

  • http1.0中,如果想要继续保持连接

    • 浏览器需要在请求头中添加connection:keep-alive
    • 服务器需要在响应头中添加connection:keep-alive
    • 当客户端再次发请求时,就会使用同一个连接,直到一方中断连接
  • http1.1中,所有连接默认是connection:keeep-alive

    • 不同的Web服务器会有不同的保持keep-alive的时间
    • Node中默认是5s
  1. accept-encoding:告知服务器,客户端支持的文件压缩格式,比如js文件可以使用gzip编码,对应.gz文件
  • 这个在性能优化方面是一个很重要的东西,比如我们利用react开发了一个项目,webpack打包时会对我们的项目进行丑化,但这其实只是相对来说比较小的优化,但如果我们在webpack进行了相关的配置,打包时把对应的js文件压缩成gzip格式,浏览器在接受到文件之后在进行解压就行,这样就可以有效的提高传输速率
  1. accept:告知服务器,客户端可接受文件的格式类型

response对象

返回响应结果

我们通常使用res.end方法来返回响应结果:res.end('hello')相当于是做了两部操作,首先先执行了res.write('hello'),然后再执行了一次res.end(),注意:res对象中是没有close方法的

返回状态码

Http状态码是用来表示Http响应状态的数字代码

  • Http状态码非常多,可以根据不同的情况,给客户端返回不同的状态码

  1. 设置状态码方式1:直接给res对象上的statusCode属性赋值
const serve1 = http.createServer((req, res) => {
  res.statusCode = 401
  res.end()
})
  1. 设置状态码方式2:通过res.writeHead方法和响应头一起设置,但我们可以单独只设置状态码
const serve1 = http.createServer((req, res) => {
  res.writeHead(400)
  res.end()
})

设置响应头

  1. 设置方式一:res.setHeaders方法可以通过传参的方式设置我们的响应头部,但是它一次性只能设置一个字段
const serve1 = http.createServer((req, res) => {
  res.setHeader('content-type', 'application/html')
  res.end()
})
  1. 设置方式二:通过res.writeHead连同状态码一起设置,我们可以在第二个参数中以键值对的方式设置响应头
const serve1 = http.createServer((req, res) => {
  res.writeHead(403, {
    // 我们还可以指定对应的字符编码,否则浏览器不知道该以哪种编码进行解析
    "content-type": 'text/html;charset=utf8'
  })
  res.end('<h2>你好!</h2>')
})

浏览器会根据不同的content-type去解析服务器响应的内容,即使是html标签也可以被识别