第五章:HTTP 协议

时间:2024-04-06 18:05:08

HTTP 协议

文章目录

  • HTTP 协议
    • @[toc]
    • 一、概念
    • 二、请求报文的组成
    • 三、HTTP 的请求行
    • 四、HTTP 的请求头
    • 五、HTTP 的请求体
    • 六、响应报文的组成
    • 七、创建 HTTP 服务
      • 7-1.操作步骤
      • 7-2.测试
      • 7-3.注意事项
    • 八、浏览器查看 HTTP 报文
      • 8-1.查看请求行与请求头
      • 8-2.查看请求体
      • 8-3.查看 URL 查询字符串
      • 8-4.查看响应行与响应头
      • 8-5.查看响应体
    • 九、获取 HTTP 请求报文
      • 9-1.练习
    • 十、获取 HTTP 响应报文
      • 10-1.练习
      • 10-2.优化:
      • 10-3.扩展
    • 十一、网页资源的基本加载过程
    • 十二、静态资源服务
      • 12-1.网站根目录或静态资源目录
      • 12-2.网页中的 URL
        • (1)绝对路径
        • (2)相对路径
        • (3)网页中使用 URL 的场景小结
      • 12-3.设置资源类型(mime 类型)
    • 十三、GET 和 POST 请求
      • 13-1.GET 和 POST 请求场景小结
      • 13-2.GET 和 POST 请求的区别

一、概念

HTTP(Hypertext Transport Protocol)协议;中文叫超文本传输协议,是一个基于TCP/IP的应用层通信协议

这个协议详细规定了 浏览器 和万维网 服务器 之间互相通信的规则。

协议中主要规定了两个方面的内容:

  • 客户端:用来向服务器发送数据,可以被称之为请求报文
  • 服务端:向客户端返回数据,可以被称之为响应报文

报文:可以简单理解为就是一堆字符串

二、请求报文的组成

  • 请求行
  • 请求头
  • 空行
  • 请求体

请求报文的组成

三、HTTP 的请求行

HTTP 的请求行是由 请求方法URLHTTP版本号 组成的。如下所示:

请求行

  • 请求方法:

    方法 作用
    GET 主要用于获取数据
    POST 主要用于新增数据
    PUT / PATCH 主要用于更新数据
    DELETE 主要用于删除数据
    HEAD / OPTIONS / CONNECT / TRACE 使用相对较少
  • 请求 URL(统一资源定位器)

    例如:http://www.baidu.com:80/index.html?a=100&b=200#logo

    • http:协议(https、ftp、ssh等)
    • www.baidu.com:域名(也可以是 IP 地址)
    • 80:端口号
    • /index.html:路径
    • a=100&b=200:查询字符串
    • #logo:哈希(锚点链接)
  • HTTP 协议版本号

四、HTTP 的请求头

请求头的格式:『头名:头值』。如下所示:

请求头

常见的请求头有:

请求头 解释
Host 主机名
Connection 连接的设置 keep-alive(保持连接);close(关闭连接)
Cache-Control 缓存控制 max-age = 0(没有缓存)
Upgrade-Insecure-Requests 将网页中的 http 请求转化为 https 请求(很少用)老网站升级
User-Agent 用户代理,客户端字符串标识,服务器可以通过这个标识来识别这个请求来自哪个客户端,一般在PC端和手机端的区分
Accept 设置浏览器接收的数据类型
Accept-Encoding 设置接收的压缩方式
Accept-Language 设置接收的语言 q = 0.7 为喜好系数,满分为1
Cookie 后面会有单独的

可以通过该网址,去查看请求头的含义:https://developer.mozilla.org/zh-CN/docs/Web/HTTP/Headers

五、HTTP 的请求体

请求体内容的格式是非常灵活的:

  • (可以是空的)==> GET请求
  • (也可以是字符串,还可以是JSON)==> POST请求

例如:

  • 字符串:keywords=手机&price=2000
  • JSON:{"keywords":"手机", "price":2000}

六、响应报文的组成

  1. 响应行

    HTTP/1.1 200 OK
    
    • HTTP/1.1:HTTP协议版本号

    • 200:响应状态码

      常见的状态码:

      状态码 含义 状态描述
      200 请求成功 OK
      403 禁止请求 Forbidden
      404 找不到资源 Not Found
      500 服务器内部错误 Internal Server Error

      还有一些状态码,参考:https://developer.mozilla.org/zh-CN/docs/Web/HTTP/Status

      状态码的分类:

      状态码 含义
      1xx 信息响应
      2xx 成功响应
      3xx 重定向消息
      4xx 客户端错误响应
      5xx 服务端错误响应
    • OK:响应状态描述

    响应状态码和响应字符串关系是一一对应的。

  2. 响应头

    Cache-Control:缓存控制	private:私有的,只允许客户端缓存数据
    Connection:链接设置
    Content-Type:text/html;charset=utf-8  设置响应体的数据类型以及字符集,响应体为html,字符集为utf-8
    Content-Length:响应体的长度,单位为字节
    
  3. 空行

  4. 响应体

    响应体的内容格式是非常灵活的,常见的响应体格式有:HTML、CSS、JavaScript、图片、视频、JSON

响应报文的组成

七、创建 HTTP 服务

使用 node.js 创建 HTTP 服务

7-1.操作步骤

// 1.导入 http 模块
const http = require('http')

// 2.创建服务对象(create:创建;server:服务)
// request 意为请求,是对请求报文的封装对象,通过 request 对象可以获得请求报文的数据
// response 意为响应,是对响应报文的封装对象,通过 response 对象可以设置响应报文    
const server = http.createServer((request, response) => {
    response.end('Hello Http Server')    // 设置响应体并结束这个响应
})

// 3.监听端口,启动服务
server.listen(9000, () => {
    console.log('服务已经启动......')
})

http.createServer 里的回调函数的执行时机:当接收到 HTTP 请求的时候,就会执行

7-2.测试

浏览器请求对应端口

http://127.0.0.1:9000

浏览器请求端口

7-3.注意事项

  1. 命令行 ctrl + c 停止服务

  2. 当服务启动后,更新代码 必须重启服务才能生效

  3. 响应内容中文乱码的解决办法

    response.setHeader('content-type', 'text/html;charset=utf-8')
    
  4. 端口号被占用

    Error: listen EADDRINUSE: address already in use :::9000
    
    • 关闭当前正在运行监听端口的服务(使用较多
    • 修改其他端口号
  5. HTTP 协议默认端口是80。HTTPS 协议的默认端口是443,HTTP 服务开发常用端口有3000,8080,8090,9000等

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

八、浏览器查看 HTTP 报文

点击步骤:

查看HTTP报文

8-1.查看请求行与请求头

查看请求行与请求头

8-2.查看请求体

查看请求体

8-3.查看 URL 查询字符串

查看 URL 查询字符串

8-4.查看响应行与响应头

查看响应行与响应头

8-5.查看响应体

查看响应体

九、获取 HTTP 请求报文

想要获取请求的数据,想要通过 request 对象

含义 语法 重点掌握
请求方法 request.method *
请求版本 request.httpVersion
请求路径 request.url *
URL 路径 require(‘url’).parse(request.url).pathname *
URL 查询字符串 require(‘url’).parse(request.url, true).query *
请求头 request.headers *
请求体 request.on(‘data’, chunk => {})
request.on(‘end’, () => {})

注意事项:

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

9-1.练习

按照以下要求搭建 HTTP 服务:

请求类型(方法) 请求地址 响应体结果
get /login 登录页面
get /reg 注册页面
// 1.导入 http 模块
const http = require('http')

// 2.创建服务对象
const server = http.createServer((request, response) => {
    // 获取请求的方法
    let {method} = request
    // 获取请求的 url 路径
    let {pathname} = new URL(request.url, 'http://127.0.0.1')
    // 设置响应头信息解决中文乱码问题
    response.setHeader('content-type', 'text/html;charset=utf-8')
    // 判断
    if(method === 'GET' && pathname === '/login') {
        response.end('登录页面')
    }else if(method === 'GET' && pathname === '/reg') {
        response.end('注册页面')
    }else {
        response.end('Not Found')
    }
})

// 3.监听端口 启动服务
server.listen('9000', () => {
    console.log('服务已经启动.. 端口 9000 监听中...')
})

十、获取 HTTP 响应报文

作用 语法
设置响应状态码 response.statusCode
设置响应状态描述 response.statusMessage(用的非常少
设置响应头信息 response.setHeader(‘头名’, ‘头值’)
设置响应体 response.write(‘xx’)
response.end(‘xxx’)
// 导入 http 模块
const http = require('http')

// 创建服务对象
const server = http.createServer((request, response) => {
    // 1.设置响应状态码
    response.statusCode = 203
    // 新设置的响应状态码会覆盖掉上一次设置的
    response.statusCode = 404
    response.statusCode = 200
    // 2.设置响应状态描述
    response.statusMessage = 'iloveyou'
    // 3.设置响应头
    response.setHeader('content-type', 'text/html;charset=utf-8')
    response.setHeader('Server', 'Node.js')
    response.setHeader('myHeader', 'test test test')
    // 设置多个同名但不同值的响应头
    response.setHeader('test', ['a', 'b', 'c'])
    // 4.响应体的设置(可以有多个 write,但 end 有且只有一个,否则会发送报错 write after end)
    response.write('love')
    response.write('love')
    response.write('love')
    response.write('love')
    response.end('云墨书生')
    response.end('xxx')
})

// 监听端口 启动服务
server.listen(9000, () => {
    console.log('服务已经启动...')
})

write 和 end 的两种使用情况:

// 1.write 和 end 的结合使用,响应体相对分散
response.write('xxx')
response.write('xxx')
response.write('xxx')
response.end()	// 每一个请求,在处理的时候都必须要执行 end 方法的

// 2.单独使用 end 方法,响应体相对集中
response.end('xxx')

10-1.练习

需求: 搭建 HTTP 服务,响应一个 4 行 3 列的表格,并且要求表格有 隔行换色效果,且 点击 单元格能 高亮显示 并且再次 点击变回原来的颜色

// 导入 http 模块
const http = require('http')

// 创建服务对象
const server = http.createServer((request, response) => {
    response.end(`
        <!DOCTYPE html>
        <html lang="zh-CN">
        <head>
            <meta charset="UTF-8">
            <meta name="viewport" content="width=device-width, initial-scale=1.0">
            <title>Document</title>
            <style>
                td {
                    padding: 20px 40px;
                }

                table tr:nth-child(odd) {
                    background: #aef;
                }

                table tr:nth-child(even) {
                    background: #fcb;
                }

                .color {
                    background: #222;
                }

                table, td {
                    border-collapse: collapse;
                }
            </style>
        </head>
        <body>
            <table border="1">
                <tr><td></td><td></td><td></td></tr>
                <tr><td></td><td></td><td></td></tr>
                <tr><td></td><td></td><td></td></tr>
                <tr><td></td><td></td><td></td></tr>
            </table>

            <script>
                // 获取所有的 tb
                let tbs = document.querySelectorAll('td')
                // 遍历
                tbs.forEach(item => {
                    item.onclick = function() {
                        if(this.classList.contains("color")) {
                            this.classList.remove("color")
                        }else {
                            this.classList.add("color")
                        }
                    }
                })
            </script>
        </body>
        </html>
    `)
})

// 监听端口 启动服务
server.listen(9000, () => {
    console.log('服务已经启动...')
})

10-2.优化:

将 html 代码与 Node.js 代码分开,用 fs 模块中的文件读取方法,读取 html 文件内容,再将读取到的内容放入响应体中

// 导入 http 模块
const http = require('http')
// 导入 fs 模块
const fs = require('fs')

// 创建服务对象
const server = http.createServer((request, response) => {
    // 通过读取文件的方法,读取 table.html 文件
    let html = fs.readFileSync(__dirname + '/table.html')
    response.end(html)  // end 方法的参数可以是字符串,还可以是 Buffer
})

// 监听端口 启动服务
server.listen(9000, () => {
    console.log('服务已经启动...')
})

这样我们就不必在 js 中去写 html 代码,不仅便于编写代码,还便于进行测试。

10-3.扩展

当将 html 文件中的 css 与 js 分开再重新导入到 html 文件,这时再按照上述写法,将无法实现预期效果

原因: 因为无论响应多少次,最终得到的响应体依旧是 html 文件中的内容,而并非是 css 或 js 文件中的内容。
解决方法: 先获取请求的路径,再根据路径去判断当前的请求是谁的,然后响应对应的响应体即可。
缺点: 一旦 css 文件或 js 文件过多,那么我们就需要不断的进行 if 判断,略显繁琐。

// 导入 http 模块
const http = require('http')
// 导入 fs 模块
const fs = require('fs')

// 创建服务对象
const server = http.createServer((request, response) => {
    // 获取请求的路径
    let {pathname} = new URL(request.url, 'http://127.0.0.1')
    if(pathname === '/') {
        // 读取文件内容
        let html = fs.readFileSync(__dirname + '/table.html')
        response.end(html)
    }else if(pathname === '/index.css') {
        // 读取文件内容
        let css = fs.readFileSync(__dirname + '/index.css')
        response.end(css)
    }else if(pathname === '/index.js') {
        // 读取文件内容
        let js = fs.readFileSync(__dirname + '/index.js')
        response.end(js)
    }else {
        response.statusCode = 404
        response.end('<h1>404 Not Found</h1>')
    }
})

// 监听端口 启动服务
server.listen(9000, () => {
    console.log('服务已经启动...')
})

十一、网页资源的基本加载过程

网页资源的基本加载过程

网页资源的加载都是循序渐进的,首先获取 HTML 的内容,然后解析 HTML 在发送其他资源的请求,如 CSS、JavaScript、图片等。理解了这个内容对于后续的学习与成长有非常大的帮助

十二、静态资源服务

静态资源是指 内容长时间不发生改变的资源,例如图片、视频、CSS 文件、JS 文件、HTML 文件、字体文件等。

动态资源是指 内容经常更新的资源,例如百度首页、网易首页、京东搜索列表页面等。

12-1.网站根目录或静态资源目录

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

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

答:网站根目录就是 vscode 所打开的文件夹。

12-2.网页中的 URL

网页中的 URL 主要分为两大类:相对路径与绝对路径

(1)绝对路径

绝对路径可靠性强,而且相对容易理解,在项目中运用较多

形式 特点
http://atguigu.com/web(完整) 直接向目标资源发送请求,容易理解。网站的外链会用到此形式
//atguigu.com/web(省略协议) 与当前页面 URL 的协议拼接形成完整 URL 再发送请求。大型网站用的比较多
/web(省略协议、域名、端口) 与当前页面 URL 的协议、主机名、端口拼接形成完整 URL 再发送请求。中小型网站
(2)相对路径

相对路径在发送请求时,需要与当前页面 URL 路径进行 计算,得到完整 URL 后,再发送请求,学习阶段用的较多。

例如当前网页 url 为 http://127.0.0.1:5500/05-HTTP/page/table.html

形式 最终的 URL
./css/app.css http://127.0.0.1:5500/05-HTTP/page/css/app.css
js/app.js http://127.0.0.1:5500/05-HTTP/page/js/app.js
…/img/logo.png http://127.0.0.1:5500/05-HTTP/img/logo.png
…/…/mp4/show.mp4 http://127.0.0.1:5500/05-HTTP/mp4/show.mp4
(3)网页中使用 URL 的场景小结

包括但不限于如下场景:

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

12-3.设置资源类型(mime 类型)

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

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

HTTP 服务可以设置响应头 Content-Type 来表明响应体的 MIME 类型,浏览器会根据该类型决定如何处理资源。

下面是常见文件对应的 mime 类型:

html: 'text/html',
css: 'text/css',
js: 'text/javascript',
png: 'image/png',
jpg: 'image/jpeg',
gif: 'image/gif',
mp4: 'video/mp4',
mp3: 'audio/mpeg',
json: 'application/json'

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

未知资源类型的下载

静态资源服务代码实例如下:

/**
 * 创建一个 HTTP 服务,端口为 9000,满足如下需求:
 * GET  /index.html         响应    page/index.html 的文件内容
 * GET  /css/app.css        响应    page/css/app.css 的文件内容
 * GET  /images/logo.png    响应    page/images/logo.png 的文件内容
 */

// 导入 http 模块
const http = require('http')
// 导入 fs 模块
const fs = require('fs')
// 导入 path 模块
const path = require('path')
// 声明一个变量
const mimes = {
    html: 'text/html',
    css: 'text/css',
    js: 'text/javascript',
    png: 'image/png',
    jpg: 'image/jpeg',
    gif: 'image/gif',
    mp4: 'video/mp4',
    mp3: 'audio/mpeg',
    json: 'application/json'
}

// 创建服务对象
const server = http.createServer((request, response) => {
    // 获取请求 url 的路径
    let {pathname} = new URL(request.url, 'http://127.0.0.1')
    // 通过 if 来判断请求 url 的路径,来响应不同的静态资源,这种方法是不便捷的
    // 优化:先拼接好文件路径,再去读取该文件
    // 声明一个变量
    let root = __dirname + '/page'
    // 拼接文件路径
    let filePath = root + pathname
    // 读取文件 fs 异步
    fs.readFile(filePath, (err, data) => {
        if(err) {
            response.setHeader('content-type', 'text/html;charset=utf-8')
            response.statusCode = 500
            response.end('读取文件失败')
            return
        }
        // 获取文件的后缀名
        let ext = path.extname(filePath).slice(1)
        // 获取对应的类型
        let type = mimes[ext]
        // 判断
        if(type) {
            // 解决乱码问题:在 mime 类型后加上 ';charset=utf-8'
            response.setHeader('content-type', type + ';charset=utf-8')
        }else {
            response.setHeader('content-type', 'application/octet-stream')
        }
        response.end(data)
    })
})

// 监视端口 启动服务
server.listen(9000, () => {
    console.log('服务已经启动..')
})

十三、GET 和 POST 请求

13-1.GET 和 POST 请求场景小结

GET 请求的情况:

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

POST 请求的情况:

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

13-2.GET 和 POST 请求的区别

GET 和 POST 是 HTTP 协议请求的两种方式。有以下几种区别:

  • GET 主要用来获取数据,POST 主要用来提交数据
  • GET 带参数请求是将参数缀到 URL 之后,在地址栏中输入 url 访问网站的就是 GET 请求;POST 带参数请求是将参数放到请求体中
  • POST 请求相对 GET 安全一些,因为 GET 请求在浏览器中参数会暴露在地址栏上
  • GET 请求大小有限制,一般为 2K,而 POST 请求则没有大小限制