• node 学习 - HTTP模块


    HTTP 协议

    初识 HTTP 协议

    Hypertext Transfer Protocol (超文本传输协议)
    互连网应用最广泛的协议之一
    协议:双方必须共同遵从的一组约定
    http 协议对浏览器和服务器之间的通信进行约束
    请求 => 请求报文
    响应 => 响应报文

    HTTP报文

    请求报文结构:

    • 请求行:请求方法 + URL + HTTP版本号 GET [https://www.baidu.com/](https://www.baidu.com/) HTTP/1.1
      • 请求方法:常用的有 GET/POST/PUT/PATCH/DELETE,还有一些使用相对比较少的,了解即可,比如:HEAD/OPTIONS/CONNECT/TRACE
      • URL(Uniform Resource Locator 的缩写,统一资源定位符):其本身也是一个字符串,定位资源
        • 协议名
        • 主机名
        • 端口号
        • 路径
        • 查询字符串
      • HTTP版本号
        • 1.0:1996年发布
        • 1.1:1999年发布
        • 2:2015年发布
        • 3:2018年发布
    • 请求头:有一系列的键值对组成(MDN HTTP Header)
    • 空行
    • 请求体:请求头的内容格式非常灵活,可以设置任意内容,只要和后端商量好

    响应报文

    • 响应行
      • HTTP版本号
      • 响应状态码
        • 1xx:信息响应
        • 2xx:成功响应(200: 请求成功)
        • 3xx:重定向响应
        • 4xx:客户端错误响应(403:禁止请求/404:找不到资源)
        • 5xx:服务端错误响应(500:服务器内部错误)
      • 响应状态描述,一般来说是字符串,保持和状态码一一对应(HTTP响应状态码
        • 200: OK
        • 403:Forbidder
        • 404:Not Found
        • 500:Internal Server Error
    • 响应头(https://developer.mozilla.org/zh-CN/docs/Web/HTTP/Headers
    • 空行
    • 响应体 -> 响应体的内容格式是非常灵活的,常见的响应体格式有:
      • HTML
      • CSS
      • JavaScript
      • 图片
      • 视频
      • JSON

    网络基础概念

    IP的介绍

    IP 也被称为【IP地址】,本身是一个数字标识(32 Bit 二进制的数字),将其拆分分组转为10进制并用.分割,例如:192.168.1.3
    IP用来标识网络设备,用于设备通信

    IP的分类

    IP为32位的二进制数,即最大为2的32次方,IP不够用
    共享IP:共享公网 IP

    • 区域共享
    • 家庭共享
      • 同一个路由器:局域网 IP 或 私网IP,设备间可以相互通信

    本机回环IP地址:

    • 127.0.0.1 ~127.255.255.254,这个区间的IP地址都是回环地址,指向当前本机

    局域网 IP (私网 IP):

    • 192.168.0.0 ~ 192.168.255.255
    • 172.16.0.0 ~ 172.31.255.255
    • 10.0.0.0 ~ 10.255.255.255

    广域网 IP (公网 IP)

    • 除上述之外

    IP 地址分类

    端口

    应用程序的数字标识
    一台现代计算机有 65536 个端口(0 ~ 65535)
    一个应用程序可以使用一个或多个端口
    作用:实现不同主机应用程序之间的通信

    创建 HTTP 服务

    // 1. 引入 http 模块
    const http = require('http')
    
    // 2. 创建服务对象
    const service = http.createServer((request, response) => {
      // request=>请求报文的封装对象
      // response=>对响应报文的封装
      response.end('hello world') //设置响应体,并结束服务
    })
    
    // 3. 监听端口,启动服务
    service.listen(9000, () => {
      // 服务启动成功后才会执行
      console.log('服务启动成功')
    })
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15

    HTTP 服务注意事项

    1. 命令行ctrl + c停止服务
    2. 当服务器启动后,更新代码必须重启服务后才能生效
    3. 响应内容中文乱码的解决办法
    response.setHeader('content-type','text/html;charset=utf-8');
    
    • 1
    1. 端口号被占用
    Error: listen EADDRINUSE: address already in use :::9000
    
    • 1
      5. 关闭当前正在运行监听端口的服务(`使用较多`)
      6. 修改其它端口号
    
    • 1
    • 2
    1. HTTP 协议默认端口是 80,HTTPS 协议的默认端口是443。HTTP 服务开发常用的端口有 3000,8080,8090,9000 等

    如果端口被其它程序占用,可以使用资源监视器找到占用端口的程序,然后使用任务管理器关闭对应的程序

    浏览器中查看 HTTP 报文

    浏览器控制台,网络

    提取 HTTP 请求报文

    image.png

    const http = require('http')
    const service = http.createServer((request, response) => {
      // 1. 获取请求的方法
      // console.log('  request.method :>> ',   request.method);
      // 2. 获取请求的 url
      // console.log('request.url :>> ', request.url); // 只包含 url 中的路径与查询字符串
      // 3. 获取 HTTP 协议的版本号
      // console.log('request.httpVersion :>> ', request.httpVersion);
      // 4. 获取 HTTP 的请求头
      // console.log('request.headers :>> ', request.headers);
      response.end('你好,世界!') //设置响应体,并结束服务
    })
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12

    注意事项

    1. request.url 只能获取路径以及查询字符串,无法获取 URL 中的域名以及协议的内容
    2. request.headers 将请求信息转化成一个对象,并将属性名都转化成了『小写』
    3. 关于路径:如果访问网站的时候,只填写了 IP 地址或者是域名信息,此时请求的路径为『 / 』
    4. 关于 favicon.ico:这个请求是属于浏览器自动发送的请求

    获取请求体

    //这种方法了解就行,有更好用的办法
    const service = http.createServer((request, response) => {
      let body = ''
      request.on('data', chunk => {
        body += chunk
      })
      request.on('end', () => {
        console.log('body :>> ', body);
        response.end('htllo http')
      })
    })
    
    service.listen('9000', () => {
      console.log('服务请求中……')
    })
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15

    提取 HTTP 报文中的请求路径及字符串

    const url = require('url');
    //方法一,使用 url.parse, 但是该语法已被废弃
    const service = http.createServer((request, response) => {
      // url.parse 的第二个参数为 true 时,查询字符串 query 将会转换为对象
      const res = url.parse(request.url, true)
      console.log('pathname :>> ', res.pathname);
      console.log('query :>> ', res.query);
      console.log('query :>> ', res.query.isTest);
      
      response.end('url')
    })
    
    //方法二:使用 new URL()
    const service = http.createServer((request, response) => {
      const res = new URL(request.url, 'http://127.0.0.1')
      const keyword = res.searchParams.get('search')
      console.log('res :>> ', res);
      console.log('keyword :>> ', keyword);
      response.end('url')
    })
    service.listen('9000', () => {
      console.log('服务请求中……')
    })
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24

    设置 HTTP 响应报文

    const http = require('http');
    
    const service = http.createServer((request, response) => {
      // 1. 设置响应状态码
      response.statusCode = 203
      // 2. 设置响应状态描述,几乎不用,响应状态码一般和响应状态描述一一对应
      // response.statusMessage = 'test'
      // 3. 设置响应头, 响应头可以设置多个,也可以使用数组设置同名的响应头
      response.setHeader('test', ['a','b','c'])
      response.setHeader('test2', 'test-2')
      // 4. 设置响应体, 响应体可以设置多个,多个的响应体会自动拼接
      // 一般使用 write 设置了响应体后,不会再在 end 中设置响应体
      response.write('test')
      response.write('test2')
    
      // 每一个请求必须有一个,且只有一个 end
      response.end()
    })
    
    service.listen('9000', () => {
      console.log('服务启动中……')
    })
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23

    引入网页外部资源

    如果在 html 文件中引入外部资源,比如 css 和 js 文件,那在请求的时候,会调用多次接口,分开请求外部资源
    所以,需要区分开请求的资源,不然都会返回同样的数据

    //方法一:在 createServer 回调函数中区分需要获取的资源
    //这种区分方法并不好,如果有大量外部资源就很繁琐,需要优化
    //可以通过搭建静态资源服务的形式优化
    const http = require('http');
    const fs = require('fs');
    const path = require('path')
    
    const service = http.createServer((request, response) => {
      const { pathname } = new URL(request.url, 'http://127.0.0.1')
      if(pathname === '/'){
        const filePath = path.resolve(__dirname , 'table/index.html')
        const html = fs.readFileSync(filePath)
        response.end(html)
      }else if(pathname === '/index.css'){
        const filePath = path.resolve(__dirname , 'table/index.css')
        const css = fs.readFileSync(filePath)
        response.end(css)
      }
      else if(pathname === '/index.js'){
        const filePath = path.resolve(__dirname , 'table/index.js')
        const js = fs.readFileSync(filePath)
        response.end(js)
      }
      else{
        response.end('404 not found')
      }
    
    })
    
    service.listen('9000', () => {
      console.log('服务启动中……')
    })
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33

    静态资源服务

    静态资源是指内容长时间不发生改变的资源,例如图片,视频,css文件,HTML文件,字体文件等
    动态资源是指内容经常更新的资源,例如百度首页,网易首页,京东搜索列表页面等

    搭建静态资源服务

    //方法二:搭建静态资源服务
    const http = require('http');
    const fs = require('fs');
    const path = require('path')
    
    const service = http.createServer((request, response) => {
      const { pathname } = new URL(request.url, 'http://127.0.0.1')
      const filePath = path.resolve(__dirname , 'table' + pathname)
      fs.readFile(filePath, (err, data) => {
        if(err){
          response.statusCode = 500;
          response.end('404 not found')
          return
        }
        response.end(data)
      })
    })
    
    service.listen('9000', () => {
      console.log('服务启动中……')
    })
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21

    静态资源目录与网站根目录

    HTTP 服务在哪个文件夹中寻找静态资源,那个文件夹就是静态资源目录,也被称之为网站根目录

    思考:vscode 中使用 live-servier 访问 HTML 时,它启动的服务网站根目录是谁?是vscode打开的文件夹

    网页中的 URL

    绝对路径

    image.png

    相对路径

    相对路径不太可靠,和页面路径相关,参照页面 URL
    image.png

    使用场景

    包括但不限于如下场景:

    • a 标签 href
    • link 标签 href
    • script 标签 src
    • img 标签 src
    • video audio 标签 src
    • form 中的 action
    • AJAX 请求中的 URL

    设置资源类型(mime 类型)

    媒体类型(通常称之为 Multipurpose Internet Mail Extensions 或 MIME 类型)是一种标准,用来表示文档、文件或字节流的性质和格式。

    mime 类型结构:[type] / [subType]
    例如:text/html  text/css  image/jpeg  image/png  application/json
    
    • 1
    • 2

    HTTP 服务可以设置响应头 Content-Type 来表明响应体的 MIME 类型,浏览器会根据该类型来决定如何处理资源
    下面是常见文件对应的 mime 类型

    html: 'text/html',
    css:'text/css',
    js: 'text/javascript',
    png : 'image/png',
    jpg: 'image/jpeg',
    gif: 'image/gif',
    map4: 'video/mp4',
    mp3: 'audio/mpeg',
    json: 'application/json',
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9

    对于未知的类型,可以选择application/octet-stream类型,浏览器在遇到该类型的响应时,会对响应体内容进行对立存储,也就是我们常见的下载效果

    浏览器一般具有嗅探的功能,会自动识别请求的资源类型,但是我们设置响应头 Content-Type 来表明响应体的 MIME 类型会更规范一点

    const http = require('http');
    const fs = require('fs');
    const path = require('path')
    
    const mimes = {
      html: 'text/html',
      css:'text/css',
      js: 'text/javascript',
      png : 'image/png',
      jpg: 'image/jpeg',
      gif: 'image/gif',
      map4: 'video/mp4',
      mp3: 'audio/mpeg',
      json: 'application/json',
    }
    const service = http.createServer((request, response) => {
      const { pathname } = new URL(request.url, 'http://127.0.0.1')
      const filePath = path.resolve(__dirname , 'table' + pathname)
      fs.readFile(filePath, (err, data) => {
        if(err){
          response.statusCode = 500;
          response.end('404 not found')
          return
        }
        const extname = path.extname(filePath).slice(1)
        const type = mimes[extname]
        if(type){
          response.setHeader('content-type',type)
        }else{
          response.setHeader('content', 'application/octet-stream')
        }
        response.end(data)
      })
    })
    
    service.listen('9000', () => {
      console.log('服务启动中……')
    })
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38

    解决乱码问题

    • 在设置资源类型时加上charset=utf-8,例如
    response.setHeader('content-type','text/html;charset=utf-8')
    
    • 1
    • html 文件会在 meta 标签中设置 charset 类型,但是优先级没有在响应头中设置高
    • 资源文件会根据页面的字符集进行解析,所以js\css等文件不设置字符集,在网页上显示也是ok的

    完善错误处理

    Error 错误
    常见的有:

    • ENOEN: 没有这样的文件或目录, 404
    • EPERM: 不允许操作, 403
    • 405:请求方法不被允许
    • 500: 服务器内部错误

    GET 和 POST 请求场景小结

    场景小结

    GET 请求情况

    • 在地址栏中直接输入 url 访问
    • 点击 a 链接
    • link 标签引入 css
    • script 标签引入 js
    • video 与 audio 引入多媒体
    • img 标签引入图片
    • form 标签中的 method 为 get(不区分大小写)
    • ajax 中的 get 请求

    POST 请求情况

    • form 标签中的 method 为 post (不区分大小写)
    • AJAX 中的 post 请求

    GET 和 POST 请求的区别

    GET 和 POST 是 HTTP 协议请求的两种方式,主要有如下几个区别

    1. 作用。GET 主要用来获取数据,POST 主要用来提交数据(并不是绝对的)
    2. 参数位置。GET 带参数请求是将参数缀到 URL 之后,POST 带参数请求是将参数放到请求体中(也不是绝对的)
    3. 安全性。POST 请求相对GET 安全一些,因为在浏览器中 GET 请求参数会暴露在地址栏
    4. GET 请求大小有限制,一般为 2K,而 POST 请求则没有大小限制
  • 相关阅读:
    C++——模板进阶
    ElemetUI重置新增或修改时报错解决办法
    7.MyBatis 操作数据库(初阶)
    在Linux/Ubuntu/Debian中使用 `tee` 命令将输出显示在终端并写入文件中
    ES集群&kibana安装
    骗赞小程序(仅供恶搞)
    C#:画许多圆
    函数式接口@FunctionalInterface
    【数学】双根号求值域问题
    【Linux】浅谈进程等待
  • 原文地址:https://blog.csdn.net/tianming2018/article/details/133631689