HTTP修炼之道

一、 初识HTTP协议

1. HTTP协议的背景知识

HTTP(HyperText Transfer Protocol,超文本传输协议)是一种用于在互联网上交换数据的应用层协议。它是万维网(World Wide Web)的基础,也是浏览器和服务器之间通信的标准方式。

HTTP协议最初由蒂姆·伯纳斯-李(Tim Berners-Lee)在1989年设计,用于实现他的超文本项目。最初的版本是HTTP/0.9,只支持简单的请求和响应模式。后来,HTTP/1.0和HTTP/1.1分别在1996年和1999年发布,增加了许多新的特性,如缓存、持久连接、分块传输、虚拟主机等。HTTP/1.1是目前最广泛使用的版本,也是本文主要介绍的版本。

2. HTTP协议的概念

HTTP协议是一种无状态的、请求-响应式的协议,即客户端(通常是浏览器)向服务器发送一个请求,服务器返回一个响应,然后连接断开。每个请求和响应都遵循一定的格式,包含起始行、头部字段和可选的消息体。

HTTP协议使用统一资源标识符(Uniform Resource Identifier,URI)来标识网络上的资源,例如网页、图片、文件等。URI通常由三部分组成:方案名(scheme)、主机名(host)和路径(path)。例如,http://www.example.com/index.html就是一个URI,其中http是方案名,www.example.com是主机名,/index.html是路径。

HTTP协议使用不同的方法(method)来表示对资源的不同操作,例如GET、POST、PUT、DELETE等。方法通常写在请求的起始行中,表示客户端想要对服务器做什么。例如,GET方法表示客户端想要获取服务器上的某个资源,POST方法表示客户端想要向服务器发送一些数据。

HTTP协议使用状态码(status code)来表示响应的结果,例如200、404、500等。状态码通常写在响应的起始行中,表示服务器对客户端请求的处理情况。例如,200状态码表示请求成功,404状态码表示请求的资源不存在,500状态码表示服务器内部错误。

3. HTTP协议的特点

HTTP协议具有以下几个特点:

  • 简单:HTTP协议的语法和语义都很简单,易于理解和实现。
  • 灵活:HTTP协议可以扩展新的方法、头部字段和消息体格式,以适应不同的需求和场景。
  • 无连接:HTTP协议每次请求和响应都是独立的,不需要建立持久的连接。这样可以节省资源和提高效率。
  • 无状态:HTTP协议不保存客户端和服务器之间的交互状态,即每个请求都是完全独立的。这样可以简化服务器的设计和管理。
  • 支持缓存:HTTP协议可以利用缓存机制来减少网络带宽消耗和提高用户体验。

二、HTTP协议分析

1. HTTP发展过程

HTTP协议经历了多次改进和发展,目前已经有四个主要版本:

  • HTTP/0.9:最早的版本,只支持GET方法和纯文本格式的响应。
  • HTTP/1.0:增加了POST、HEAD等方法,以及多种头部字段和消息体格式。引入了版本号、状态码等概念。允许建立非持久连接(non-persistent connection),即每个请求-响应对应一个TCP连接。
  • HTTP/1.1:增加了PUT、DELETE等方法,以及分块传输(chunked transfer)、虚拟主机(virtual host)、管道化(pipelining)等特性。允许建立持久连接(persistent connection),即多个请求-响应可以共用一个TCP连接。引入了缓存控制(cache control)等机制。
  • HTTP/2:基于Google的SPDY协议,使用二进制格式而非文本格式传输数据,支持多路复用(multiplexing)、服务器推送(server push)、头部压缩(header compression)等特性。兼容HTTP/1.1的语义,但提高了传输效率和性能。

2. HTTP报文解析

HTTP报文是HTTP协议中用于交换数据的格式,分为请求报文和响应报文两种。请求报文由客户端发送给服务器,响应报文由服务器发送给客户端。每种报文都包含三个部分:起始行、头部字段和消息体。

(1) Method

方法是请求报文的起始行中的第一个单词,表示客户端对服务器的操作意图。HTTP协议定义了八种标准方法,分别是:

  • GET:获取指定资源的内容。
  • HEAD:获取指定资源的头部信息。
  • POST:向指定资源提交数据,通常用于表单提交或文件上传。
  • PUT:向指定资源上传数据,通常用于更新或创建资源。
  • DELETE:删除指定资源。
  • OPTIONS:查询指定资源支持的方法和选项。
  • TRACE:回显服务器收到的请求,用于测试或诊断。
  • CONNECT:建立与指定资源的隧道连接,通常用于SSL加密。

除了这些标准方法外,HTTP协议也允许自定义其他方法,但不保证被所有服务器支持。

(2) 状态码

状态码是响应报文的起始行中的第一个数字,表示服务器对客户端请求的处理结果。HTTP协议定义了五类状态码,分别是:

  • 1xx:信息类,表示接收到请求并继续处理。
  • 2xx:成功类,表示请求已被成功接收、理解和接受。
  • 3xx:重定向类,表示需要进一步的操作才能完成请求。
  • 4xx:客户端错误类,表示请求有语法错误或请求无法实现。
  • 5xx:服务器错误类,表示服务器未能实现合法的请求。

每类状态码中又有具体的状态码和对应的原因短语,例如:

  • 200 OK:请求成功。
  • 301 Moved Permanently:永久重定向,请求的资源已被永久移动到新位置。
  • 400 Bad Request:客户端请求有语法错误,不能被服务器所理解。
  • 404 Not Found:请求失败,请求所希望得到的资源未被在服务器上发现。
  • 500 Internal Server Error:服务器内部错误,无法完成请求。

(3) RESTful API

RESTful API是一种基于HTTP协议的API设计风格,它遵循REST(Representational State Transfer,表述性状态转移)原则。REST是一种软件架构风格,它强调资源的标识、表述和链接。RESTful API利用HTTP协议中的方法、状态码、URI等元素来实现对资源的统一和规范的操作。例如:

  • 使用URI来标识资源,例如http://example.com/users表示用户集合资源,http://example.com/users/1表示用户编号为1的单个资源。
  • 使用GET方法来获取资源内容,例如GET http://example.com/users表示获取所有用户信息,GET http://example.com/users/1表示获取用户编号为1的信息。
  • 使用POST方法来创建新的资源或提交数据,例如POST http://example.com/users表示创建一个新用户,并将用户信息放在消息体中。
  • 使用PUT方法来更新或替换已有的资源,例如PUT http://example.com/users/1表示更新或替换用户编号为1的信息,并将新的用户信息放在消息体中。
  • 使用DELETE方法来删除已有的资源,例如DELETE http://example.com/users/1表示删除用户编号为1的信息。
  • 使用状态码来表示操作的结果,例如200表示成功,201表示创建成功,204表示删除成功,400表示请求错误,404表示资源不存在等。

RESTful API的优点是简单、清晰、一致、易于扩展和维护。RESTful API的缺点是可能存在性能问题、安全问题、版本控制问题等。

(4) 常用请求头以及响应头

请求头和响应头是HTTP报文中的头部字段,用于描述请求和响应的属性和参数。HTTP协议定义了许多标准的头部字段,也允许自定义其他头部字段。以下是一些常用的请求头和响应头:

  • Accept:请求头,表示客户端期望接收的媒体类型,例如Accept: text/html, application/json表示客户端可以接受HTML或JSON格式的数据。
  • Content-Type:请求头或响应头,表示消息体的媒体类型,例如Content-Type: application/json表示消息体是JSON格式的数据。
  • Host:请求头,表示请求的目标主机名和端口号,例如Host: www.example.com:80表示请求的主机名是www.example.com,端口号是80。
  • User-Agent:请求头,表示客户端的类型和版本,例如User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/92.0.4515.131 Safari/537.36表示客户端是Chrome浏览器。
  • Cookie:请求头,表示客户端发送给服务器的一些键值对数据,用于保存客户端的状态或偏好,例如Cookie: name=alice; age=18表示客户端发送了两个cookie数据,分别是name和age。
  • Set-Cookie:响应头,表示服务器发送给客户端的一些键值对数据,用于设置客户端的cookie数据,例如Set-Cookie: session_id=123456; Expires=Wed, 21 Aug 2023 07:28:00 GMT表示服务器发送了一个cookie数据,名为session_id,值为123456,并设置了过期时间为2023年8月21日。
  • Cache-Control:请求头或响应头,表示缓存相关的指令和参数,用于控制缓存的行为和有效期,例如Cache-Control: no-cache, no-store, max-age=0, must-revalidate表示不使用缓存,不存储缓存,并且每次都要向服务器验证缓存是否有效。
  • Location:响应头,表示重定向的目标URI,通常与3xx状态码一起使用,例如Location: http://www.example.com/new_page.html表示重定向到新页面。

(5) 缓存以及cookie

缓存(cache)是一种存储机制,用于提高网络传输的效率和性能。缓存可以分为客户端缓存和服务器缓存两种。客户端缓存是指浏览器或其他应用程序在本地存储一些已经请求过的资源,例如网页、图片、样式表等,以便下次访问时无需再向服务器请求,从而节省网络带宽和时间。服务器缓存是指服务器在内存或硬盘中存储一些经常被请求的资源,例如数据库查询结果、动态网页等,以便快速响应客户端的请求,从而减轻服务器的负载和压力。

HTTP协议中提供了一些头部字段和指令来控制缓存的行为和有效期,例如Cache-Control、Expires、Last-Modified、ETag等。这些头部字段和指令可以用于实现不同的缓存策略,例如强制缓存(force cache)、协商缓存(negotiate cache)、无缓存(no cache)等。不同的缓存策略有不同的优缺点,需要根据具体的场景和需求来选择合适的缓存策略。

Cookie是一种用于保存客户端状态或偏好的数据,通常由服务器发送给客户端,并由客户端保存在本地。Cookie可以用于实现一些功能,例如用户认证、会话管理、个性化设置等。HTTP协议中提供了一些头部字段来设置和传递Cookie,例如Set-Cookie、Cookie等。这些头部字段可以用于指定Cookie的名称、值、过期时间、作用域、安全性等属性。

Cookie的优点是简单、方便、易于实现。Cookie的缺点是有大小限制(通常为4KB)、有数量限制(通常为50个),并且可能存在安全风险(例如被窃取或伪造)。

js 复制代码
cookie是一种在网站和浏览器之间传递信息的小文件。它们被用来存储有关用户的数据
,例如登录凭据、个人偏好和浏览历史。当你访问一个网站时,网站会将cookie存储在你的浏览器中,
以便在你下次访问该网站时能够识别你。这样,网站可以提供个性化的内容和功能,
同时也可以追踪你的活动。

实现cookie

首先,需要导入net/http包,并创建一个HTTP处理函数来处理请求。在处理函数中,你可以使用http.Cookie结构来创建一个cookie对象,并设置其名称、值和其他属性。然后,你可以使用http.SetCookie函数将cookie添加到响应头中。

go 复制代码
package main 
import ( "fmt" 
        "net/http"
        )
func main() {
http.HandleFunc("/", setCookieHandler)
http.ListenAndServe(":8080", nil) }
func setCookieHandler(w http.ResponseWriter, r *http.Request) { 
    cookie := &http.Cookie{ Name: "mycookie", Value: "Hello, WALL-E!",} 
    http.SetCookie(w, cookie)
    fmt.Fprintln(w, "Cookie has been set!") }

在上面的示例中,我们创建了一个名为"mycookie"的cookie,并将其值设置为"Hello, WALL-E!"。然后,我们使用http.SetCookie函数将cookie添加到响应头中。最后,我们通过fmt.Fprintln将一条消息发送给浏览器,告诉它cookie已经设置成功。当你运行这个程序并访问http://localhost:8080时,浏览器将收到一个名为"mycookie"的cookie,并将其值设置为"Hello, WALL-E!"。

使用gin框架实现cookie

go 复制代码
package main 
import (
    "github.com/gin-gonic/gin" 
    "net/http" 
)
func main() {
router := gin.Default()
router.GET("/", func(c *gin.Context) {
    // 设置cookie cookie := &http.Cookie{ 
        Name: "username", 
        Value: "WALL-E", 
        Path: "/", }
        http.SetCookie(c.Writer, cookie)
        c.String(http.StatusOK, "Cookie has been set!") })
        router.Run(":8080") 
 }

3. HTTP/2协议以及HTTPS协议

HTTP/2协议是HTTP协议的最新版本,于2015年正式发布,旨在解决HTTP/1.1协议存在的一些问题,例如头部冗余、队头阻塞(head-of-line blocking)、多路复用(multiplexing)等。HTTP/2协议的主要特点有:

  • 二进制格式:HTTP/2协议使用二进制格式而非文本格式来传输数据,这样可以减少解析错误和压缩开销,并提高传输效率。
  • 多路复用:HTTP/2协议支持在一个TCP连接上同时传输多个请求和响应,这样可以减少连接数和延迟,并提高并发性能。
  • 服务器推送:HTTP/2协议支持服务器主动向客户端推送一些资源,这样可以避免客户端多次请求,并提高用户体验。
  • 头部压缩:HTTP/2协议使用HPACK算法来压缩头部字段,这样可以减少头部冗余和网络开销。

HTTPS协议是HTTP协议的安全版本,它在HTTP协议的基础上加入了SSL/TLS层,用于实现数据的加密、认证和完整性保护。HTTPS协议的主要特点有:

  • 安全:HTTPS协议使用对称加密(symmetric encryption)、非对称加密(asymmetric encryption)和哈希算法(hash algorithm)等技术来保证数据的机密性、身份性和完整性。
  • 可信:HTTPS协议使用数字证书(digital certificate)和证书颁发机构(certificate authority)等机制来验证服务器和客户端的身份和信任关系。
  • 兼容:HTTPS协议可以兼容HTTP协议,即可以使用HTTP协议的语义和格式,只是在传输层加入了SSL/TLS层。

HTTPS协议的优点是提高了网络通信的安全性和可信性。HTTPS协议的缺点是增加了计算和网络开销,以及证书管理的复杂性。

三、HTTP常用场景

1. 场景分析

HTTP协议在网络通信中有许多常用的场景,例如静态资源、登录、表单提交等。这些场景涉及到HTTP协议的不同方法、头部字段、缓存机制、安全机制等。以下是两个具体的场景分析:

(1) 静态资源

静态资源是指不需要服务器动态生成或处理的资源,例如网页、图片、样式表、脚本等。静态资源通常使用GET方法来请求,使用200状态码来响应,并使用缓存机制来提高性能和用户体验。

以今日头条网站为例,当我们访问https://www.toutiao.com/时,浏览器会向服务器请求该网页的HTML内容,并根据HTML中的引用,再向服务器请求其他的静态资源,例如CSS、JS、图片等。这些静态资源的请求和响应如下:

  • CSS:请求头中包含Accept: text/css,*/*;q=0.1表示期望接收CSS格式的数据,响应头中包含Content-Type: text/css表示消息体是CSS格式的数据,以及Cache-Control: max-age=31536000表示该资源可以在客户端缓存一年。响应状态码为200表示请求成功。
  • JS:请求头中包含Accept: */*表示期望接收任意格式的数据,响应头中包含Content-Type: application/javascript表示消息体是JS格式的数据,以及Cache-Control: max-age=31536000表示该资源可以在客户端缓存一年。响应状态码为200表示请求成功。
  • 图片:请求头中包含Accept: image/avif,image/webp,image/apng,image/svg+xml,image/*,*/*;q=0.8表示期望接收多种图片格式的数据,响应头中包含Content-Type: image/jpeg表示消息体是JPEG格式的图片,以及Cache-Control: max-age=31536000表示该资源可以在客户端缓存一年。响应状态码为200表示请求成功。

从上面的例子可以看出,今日头条网站使用了强制缓存(force cache)策略来处理静态资源,即客户端在第一次请求后就将静态资源保存在本地,并根据Cache-Control字段来判断是否需要再次向服务器请求。这样可以减少网络带宽消耗和服务器压力,并提高用户体验。

除了强制缓存外,还有一种缓存策略叫做协商缓存(negotiate cache),即客户端在每次请求前都要向服务器验证缓存是否有效,如果有效则返回304状态码和Not Modified原因短语,并使用本地缓存;如果无效则返回200状态码和新的资源内容,并更新本地缓存。这样可以保证客户端总是获取最新的资源内容,但也增加了网络开销和延迟。

为了实现协商缓存,需要使用一些头部字段来标识资源的版本和修改时间,例如Last-Modified、ETag等。例如:

  • Last-Modified:响应头,表示资源的最后修改时间,例如Last-Modified: Wed, 21 Aug 2023 07:28:00 GMT表示资源最后修改于2023年8月21日。
  • If-Modified-Since:请求头,表示客户端已有缓存的最后修改时间,例如If-Modified-Since: Wed, 21 Aug 2023 07:28:00 GMT表示客户端已有缓存的最后修改时间,用于与服务器的资源进行比较。如果客户端的缓存和服务器的资源的最后修改时间相同,则表示缓存有效,服务器返回304状态码和Not Modified原因短语,并不返回消息体;如果客户端的缓存和服务器的资源的最后修改时间不同,则表示缓存无效,服务器返回200状态码和OK原因短语,并返回新的消息体。
  • ETag:响应头,表示资源的唯一标识符,例如ETag: "5d8c72a5edda8"表示资源的标识符是5d8c72a5edda8。
  • If-None-Match:请求头,表示客户端已有缓存的标识符,例如If-None-Match: "5d8c72a5edda8"表示客户端已有缓存的标识符是5d8c72a5edda8,用于与服务器的资源进行比较。如果客户端的缓存和服务器的资源的标识符相同,则表示缓存有效,服务器返回304状态码和Not Modified原因短语,并不返回消息体;如果客户端的缓存和服务器的资源的标识符不同,则表示缓存无效,服务器返回200状态码和OK原因短语,并返回新的消息体。

从上面的例子可以看出,Last-Modified和ETag都可以用于实现协商缓存,但它们有一些区别。Last-Modified是基于时间来判断资源是否修改,而ETag是基于内容来判断资源是否修改。Last-Modified可能存在一些问题,例如时间精度不够、时区不一致、服务器时间不准确等,导致缓存失效。ETag则可以更准确地反映资源的变化,但也需要更多的计算开销。

(2) 登录

登录是指用户通过输入用户名和密码或其他方式来验证身份并访问受限制的资源或服务。登录通常使用POST方法来提交用户信息,并使用Cookie或Token等机制来保存用户状态。登录还涉及到跨域问题、表单登录、扫码登录、SSO登录等。

以今日头条网站为例,当我们点击右上角的登录按钮时,浏览器会向服务器请求一个登录页面,该页面包含了一个表单,让用户输入手机号和验证码。该表单使用POST方法向https://sso.toutiao.com/login/提交用户信息,并设置了一个隐藏字段servicehttps://www.toutiao.com/表示登录成功后要跳转到的目标页面。

该请求和响应如下:

  • 请求头中包含Content-Type: application/x-www-form-urlencoded表示消息体是表单格式的数据,以及Cookie: csrftoken=xxxxxx; tt_webid=yyyyyy表示客户端发送了两个Cookie数据,分别是csrftoken和tt_webid。
  • 请求体中包含mobile=13888888888&code=123456&service=https%3A%2F%2Fwww.toutiao.com%2F&csrfmiddlewaretoken=xxxxxx表示用户输入了手机号、验证码、目标页面和csrf令牌。
  • 响应头中包含Set-Cookie: sso_user_id=zzzzzz; Domain=.toutiao.com; Path=/; Expires=Thu, 21 Aug 2025 07:28:00 GMT; HttpOnly; Secure; SameSite=None表示服务器发送了一个Cookie数据,名为sso_user_id,值为zzzzzz,并设置了域名、路径、过期时间等属性。
  • 响应体中包含一个JSON格式的数据,如下:
go 复制代码
{
  "code": 0,
  "msg": "success",
  "data": {
    "redirect_url": "https://www.toutiao.com/"
  }
}

表示登录成功,并返回一个重定向地址。

从上面的例子可以看出,今日头条网站使用了表单登录(form login)方式来实现用户认证,即用户通过填写表单来提交用户名和密码或其他信息,并通过服务器的验证后登录。表单登录的优点是简单、直观、易于实现。表单登录的缺点是需要用户输入信息、可能存在安全风险(例如暴力破解、中间人攻击等)。

除了表单登录外,还有一种登录方式叫做扫码登录(scan code login),即用户通过扫描二维码来登录,无需输入信息。扫码登录的原理是,服务器生成一个唯一的标识符和对应的二维码,并将其显示在登录页面上;用户使用手机扫描该二维码,并在手机上确认登录;服务器检测到用户的确认后,将用户的信息返回给浏览器,并完成登录。扫码登录的优点是方便、快捷、安全。扫码登录的缺点是需要用户使用手机、可能存在网络延迟或失效等问题。

还有一种登录方式叫做SSO登录(single sign-on login),即用户通过一个统一的认证中心来登录多个应用或服务,无需重复输入信息。SSO登录的原理是,用户访问一个应用或服务时,如果没有登录,则跳转到认证中心;用户在认证中心输入用户名和密码或其他方式进行认证;认证中心验证用户身份后,生成一个令牌并返回给用户;用户携带该令牌访问其他应用或服务时,无需再次登录。SSO登录的优点是便捷、高效、统一。SSO登录的缺点是需要建立一个可信的认证中心、可能存在安全风险(例如令牌泄露或伪造等)。

为了实现用户状态的保存和管理,通常使用Cookie或Token等机制。Cookie是一种由服务器发送给客户端的数据,客户端保存在本地,并在每次请求时携带在请求头中。Cookie可以用于保存用户的身份信息、偏好设置等。Token是一种由服务器生成并返回给客户端的数据,客户端保存在本地,并在每次请求时携带在请求头或请求体中。Token可以用于保存用户的身份信息、权限信息等。

Cookie和Token都可以用于实现用户状态的保存和管理,但它们有一些区别。Cookie是基于状态(stateful)的机制,即服务器需要保存每个客户端的状态信息,并根据客户端发送的Cookie来识别和验证客户端。Token是基于无状态(stateless)的机制,即服务器不需要保存每个客户端的状态信息,而是根据客户端发送的Token来识别和验证客户端。

Cookie和Token各有优缺点,需要根据具体的场景和需求来选择合适的机制。一般来说,Cookie适合用于单域名或子域名之间的通信,而Token适合用于跨域名或跨平台之间的通信。

(3) 跨域

跨域问题是指浏览器为了保护用户的安全和隐私,限制了不同源的网页之间的数据交互。不同源是指网页的协议、域名或端口有任何一个不相同 ,例如http://a.comhttps://a.com就是不同源,因为它们的协议不同。

跨域问题会影响一些常见的网络功能,例如使用XMLHttpRequest或Fetch API发送AJAX请求,或者使用iframe标签嵌入其他网站的内容。如果浏览器检测到跨域操作,就会阻止或限制数据的传输,并报错。

为了解决跨域问题,有一些常用的方法,例如:

  • JSONP:JSONP(JSON with Padding)是一种利用script标签的src属性来发送和接收数据的技术,它不受跨域限制,因为script标签可以加载任何来源的脚本。JSONP的原理是客户端通过script标签向服务器发送一个回调函数名和其他参数,服务器返回一个调用该回调函数并传入数据的脚本,客户端执行该脚本并获取数据。JSONP的缺点是只能发送GET请求,不能发送POST或其他请求。
  • CORS:CORS(Cross-Origin Resource Sharing)是一种基于HTTP头部字段的机制,它允许服务器声明哪些来源的客户端可以访问哪些资源,并且可以控制是否允许携带Cookie等信息。CORS的原理是客户端在发送请求时会附带一个Origin字段,表示请求的来源,服务器根据该字段来判断是否允许跨域访问,并在响应头中返回一个Access-Control-Allow-Origin字段,表示允许访问的来源。CORS的优点是可以支持各种类型的请求,包括POST、PUT、DELETE等。
  • 代理:代理是一种利用服务器端来转发客户端和目标服务器之间的请求和响应的技术,它可以绕过浏览器的跨域限制,因为对于浏览器来说,只有和代理服务器进行通信。代理可以使用一些工具或软件来实现,例如http-proxy、nginx、Apache等,在vue开发项目中,可以通过修改vue.config.js来进行配置代理服务器。代理的缺点是需要额外的服务器资源和配置。

具体解决方案可以参考这篇文章:解决跨域问题的8种方案

四、HTTP实际应用

HTTP协议在实际应用中有许多工具和技术可以使用,例如Ajax、XHR、Fetch、Node.js、Axios等。这些工具和技术可以方便地发送和接收HTTP请求和响应,并处理相关的数据和事件。以下是一些具体的工具和技术介绍:

1. Ajax与XHR

Ajax(Asynchronous JavaScript and XML)是一种使用JavaScript来实现异步网络通信的技术,它可以在不刷新页面的情况下与服务器交换数据,并更新页面内容。Ajax可以提高网页的交互性和用户体验。

XHR(XMLHttpRequest)是一种JavaScript对象,它提供了一个API来发送HTTP请求和接收HTTP响应,并支持异步操作。XHR是Ajax技术的核心组件之一。

使用Ajax和XHR可以实现如下功能:

  • 创建一个XHR对象:
csharp 复制代码
js
复制代码
// 创建一个XHR对象
var xhr = new XMLHttpRequest();
  • 设置请求的方法、URL和异步标志:
kotlin 复制代码
js
复制代码
// 设置请求的方法、URL和异步标志
xhr.open("GET", "https://example.com/api/data", true);
  • 设置请求头部字段:
arduino 复制代码
js
复制代码
// 设置请求头部字段
xhr.setRequestHeader("Content-Type", "application/json");
xhr.setRequestHeader("Authorization", "Bearer xxxxxx");
  • 设置响应的事件处理函数:
javascript 复制代码
js
复制代码
// 设置响应的事件处理函数
xhr.onreadystatechange = function() {
  // 判断响应是否完成
  if (xhr.readyState === 4) {
    // 判断响应是否成功
    if (xhr.status === 200) {
      // 获取响应的消息体
      var data = JSON.parse(xhr.responseText);
      // 处理数据,例如更新页面内容
      console.log(data);
    } else {
      // 处理错误,例如提示用户
      console.error(xhr.statusText);
    }
  }
};
  • 发送请求:
scss 复制代码
js
复制代码
// 发送请求
xhr.send();

使用Ajax和XHR的优点是可以实现异步、灵活、高效的网络通信。使用Ajax和XHR的缺点是可能存在兼容性问题、安全问题、跨域问题等。

2. Fetch

Fetch是一种新的JavaScript API,它提供了一个更简洁、更强大、更一致的方式来发送HTTP请求和接收HTTP响应,并支持异步操作,返回promise对象。Fetch是Ajax技术的替代方案之一。

使用Fetch可以实现如下功能:

  • 创建并发送一个GET请求:
javascript 复制代码
js
复制代码
// 创建并发送一个GET请求
fetch("https://example.com/api/data")
  // 获取响应对象并转换为JSON格式的数据
  .then(response => response.json())
  // 处理数据,例如更新页面内容
  .then(data => console.log(data))
  // 处理错误,例如提示用户
  .catch(error => console.error(error));
  • 创建并发送一个POST请求:
javascript 复制代码
js
复制代码
// 创建并发送一个POST请求
fetch("https://example.com/api/data", {
  // 设置请求的方法为POST
  method: "POST",
  // 设置请求头部字段
  headers: {
    "Content-Type": "application/json",
    "Authorization": "Bearer xxxxxx"
  },
  // 设置请求体为JSON格式的数据
  body: JSON.stringify({
    name: "Alice",
    age: 18
  })
})
  // 获取响应对象并转换为JSON格式的数据
  .then(response => response.json())
  // 处理数据,例如更新页面内容
  .then(data => console.log(data))
  // 处理错误,例如提示用户
  .catch(error => console.error(error));

使用Fetch的优点是可以实现更简洁、更强大、更一致的网络通信。使用Fetch的缺点是可能存在兼容性问题、错误处理问题等。

3. Node.js中的http/https模块

Node.js是一种基于Chrome V8引擎的JavaScript运行环境,它可以让JavaScript在服务器端运行,并提供了一些模块来实现各种功能。Node.js中有两个模块分别用于处理HTTP和HTTPS协议,分别是http模块和https模块。

使用http/https模块可以实现如下功能:

  • 创建并启动一个HTTP服务器:
vbscript 复制代码
js
复制代码
// 引入http模块
var http = require("http");

// 创建一个HTTP服务器对象
var server = http.createServer(function(request, response) {
  // 获取请求的方法、URL和头部字段等信息
  console.log(request.method, request.url, request.headers);

  // 设置响应的状态码、头部字段和消息体等信息
  response.statusCode = 200;
  response.setHeader("Content-Type", "text/plain");
  response.write("Hello, world!");

  // 结束响应并发送给客户端
  response.end();
});

// 启动服务器并监听8080端口上的客户端请求
server.listen(8080, function() {
  console.log("Server is running at http://localhost:8080/");
});
  • 创建并发送一个HTTP客户端请求:
javascript 复制代码
js
复制代码
// 引入http模块
var http = require("http");

// 创建一个HTTP客户端请求对象
var request = http.request({
  // 设置请求的方法、URL和头部字段等信息
  method: "GET",
  hostname: "example.com",
  path: "/api/data",
  headers: {
    "Content-Type": "application/json"
  }
}, function(response) {
  // 获取响应的状态码、头部字段和消息体等信息
  console.log(response.statusCode, response.headers);

  // 设置响应的编码格式
  response.setEncoding("utf8");

  // 获取响应的消息体数据
  var data = "";
  response.on("data", function(chunk) {
    data += chunk;
  });

  // 处理响应的结束事件
  response.on("end", function() {
    // 解析响应的消息体数据为JSON格式
    data = JSON.parse(data);
    // 处理数据,例如打印到控制台
    console.log(data);
  });
});

// 处理请求的错误事件
request.on("error", function(error) {
  // 处理错误,例如打印到控制台
  console.error(error);
});

// 结束请求并发送给服务器
request.end();
  • 创建一个HTTP客户端:
javascript 复制代码
js
复制代码
var http = require("http"); // 引入http模块

var options = { // 创建一个选项对象,用于设置HTTP请求的相关参数
  hostname: "example.com", // 设置请求的主机名
  port: 80, // 设置请求的端口号
  path: "/data.json", // 设置请求的路径
  method: "GET", // 设置请求的方法
  headers: {"Content-Type": "application/json"} // 设置请求头对象,表示发送的数据是JSON格式的
};

var req = http.request(options, function(res) { // 创建一个HTTP请求,参数为选项对象和一个回调函数,该函数接收一个参数:res表示响应对象
  console.log("Status: " + res.statusCode); // 打印响应状态码
  console.log("Headers: " + JSON.stringify(res.headers)); // 打印响应头信息,使用JSON.stringify方法将响应头对象转换为字符串

  res.setEncoding("utf8"); // 设置响应数据的编码格式为utf8

  res.on("data", function(chunk) { // 设置当接收到响应数据时的事件监听器,参数为一个回调函数,该函数接收一个参数:chunk表示响应数据的一部分
    console.log("Body: " + chunk); // 打印响应数据的一部分
  });

  res.on("end", function() { // 设置当接收到所有响应数据时的事件监听器,参数为一个回调函数
    console.log("No more data in response."); // 打印结束信息
  });
});

req.on("error", function(error) { // 设置当请求发生错误时的事件监听器,参数为一个回调函数,该函数接收一个参数:error表示错误对象
  console.error(error); // 打印错误信息
});

req.end(); // 结束请求,并发送数据给服务器
  • 创建一个HTTPS服务器:
javascript 复制代码
js
复制代码
var https = require("https"); // 引入https模块
var fs = require("fs"); // 引入fs模块

var options = { // 创建一个选项对象,用于设置HTTPS服务器的相关参数
  key: fs.readFileSync("server.key"), // 设置服务器的私钥文件,使用fs模块的readFileSync方法来同步读取文件内容,并赋值给key属性
  cert: fs.readFileSync("server.crt") // 设置服务器的证书文件,使用fs模块的readFileSync方法来同步读取文件内容,并赋值给cert属性
};

var server = https.createServer(options, function(req, res) { // 创建一个HTTPS服务器,参数为选项对象和一个回调函数,该函数接收两个参数:req表示请求对象,res表示响应对象
  res.writeHead(200, {"Content-Type": "text/plain"}); // 设置响应头中的状态码为200,表示请求成功,以及Content-Type字段为text/plain,表示响应数据是纯文本格式的
  res.end("Hello World!"); // 结束响应,并发送数据"Hello World!"给客户端
});

server.listen(3000, function() { // 让服务器监听3000端口,并设置一个回调函数,在服务器启动后执行
  console.log("Server is running at https://localhost:3000"); // 打印服务器地址信息
});
  • 创建一个HTTPS客户端:
javascript 复制代码
js
复制代码
var https = require("https"); // 引入https模块

var options = { // 创建一个选项对象,用于设置HTTPS请求的相关参数
  hostname: "example.com", // 设置请求的主机名
  port: 443, // 设置请求的端口号
  path: "/data.json", // 设置请求的路径
  method: "GET", // 设置请求的方法
  headers: {"Content-Type": "application/json"} // 设置请求头对象,表示发送的数据是JSON格式的
};

var req = https.request(options, function(res) { // 创建一个HTTPS请求,参数为选项对象和一个回调函数,该函数接收一个参数:res表示响应对象
  console.log("Status: " + res.statusCode); // 打印响应状态码
  console.log("Headers: " + JSON.stringify(res.headers)); // 打印响应头信息,使用JSON.stringify方法将响应头对象转换为字符串

  res.setEncoding("utf8"); // 设置响应数据的编码格式为utf8

  res.on("data", function(chunk) { // 设置当接收到响应数据时的事件监听器,参数为一个回调函数,该函数接收一个参数:chunk表示响应数据的一部分
    console.log("Body: " + chunk); // 打印响应数据的一部分
  });

  res.on("end", function() { // 设置当接收到所有响应数据时的事件监听器,参数为一个回调函数
    console.log("No more data in response."); // 打印结束信息
  });
});

req.on("error", function(error) { // 设置当请求发生错误时的事件监听器,参数为一个回调函数,该函数接收一个参数:error表示错误对象
  console.error(error); // 打印错误信息
});

req.end(); // 结束请求,并发送数据给服务器

使用http/https模块的优点是可以实现低层次、高性能、高定制的网络通信。使用http/https模块的缺点是可能存在复杂性、冗余性、可读性等问题。

4. Axios

Axios是一种基于Promise的JavaScript库,它可以用于浏览器和Node.js中发送HTTP请求和接收HTTP响应,并支持异步操作。Axios是Ajax技术的替代方案之一。

使用Axios可以实现如下功能:

  • 创建并发送一个GET请求:
javascript 复制代码
js
复制代码
// 引入axios库
var axios = require("axios");

// 创建并发送一个GET请求
axios.get("https://example.com/api/data")
  // 处理响应的数据,例如更新页面内容
  .then(response => console.log(response));
  • 或者更为详细些:
javascript 复制代码
js
复制代码
var axios = require("axios"); // 引入Axios模块
var instance = axios.create({ // 创建一个Axios实例,参数为配置对象
  baseURL: "http://example.com", // 设置基础URL,即所有请求的公共部分
  timeout: 1000, // 设置超时时间,单位为毫秒
  headers: {"Content-Type": "application/json"} // 设置请求头对象,表示发送的数据是JSON格式的
});
  • 使用Axios实例发送请求,并返回一个Promise对象:
javascript 复制代码
js
复制代码
instance.get("/data.json") // 使用get方法发送请求,参数为URL或路径字符串,并返回一个Promise对象
  .then(function(response) { // 使用then方法设置当请求完成时的回调函数,参数为Response对象
    console.log(response.data); // 打印响应数据
  })
  .catch(function(error) { // 使用catch方法设置当请求发生错误时的回调函数,参数为错误对象
    console.error(error); // 打印错误信息
  });
  • 使用async/await语法来简化异步操作:
javascript 复制代码
js
复制代码
async function getData() { // 定义一个异步函数,用于获取数据
  try { // 使用try语句来捕获可能发生的错误
    var response = await instance.get("/data.json"); // 使用await关键字来等待Promise对象的结果,并赋值给response变量
    console.log(response.data); // 打印响应数据
  } catch (error) { // 使用catch语句来处理错误
    console.error(error); // 打印错误信息
  }
}

getData(); // 调用异步函数

使用Axios可以实现与服务器的异步通信,而且有一些优势,例如支持Promise、支持拦截器(interceptor)、支持取消请求、支持进度事件、支持跨域请求等。Axios的缺点是不支持低版本的浏览器、不支持Cookie等。

5. 用户体验

用户体验(user experience,UX)是指用户在使用网络服务或应用时的感受和评价,它涉及到用户的需求、期望、情感、满意度等方面。用户体验是网络通信的重要目标之一,它可以影响用户的行为和忠诚度。为了提高用户体验,可以从以下两个方面进行优化:

(1) 网络优化

网络优化是指通过使用一些技术和策略来提高网络传输的速度和效率,从而减少用户的等待时间和网络开销。网络优化的方法有:

  • CDN:CDN(Content Delivery Network,内容分发网络)是一种利用分布在不同地理位置的服务器来缓存和分发静态资源的技术,它可以使用户从最近的服务器获取资源,从而降低延迟和带宽消耗。CDN还可以提供负载均衡、安全防护、智能路由等功能。CDN可以与HTTP/2协议结合使用,以进一步提高传输效率和性能。HTTP/2协议是HTTP协议的最新版本,它支持多路复用、服务器推送、头部压缩等特性。根据一项研究,开启HTTP/2协议的CDN比未开启HTTP/2协议的CDN在响应时间、请求数、字节数等方面都有显著的改善。
  • 预解析和预连接:预解析(pre-resolve)和预连接(pre-connect)是一种利用HTML中的<link>标签来告诉浏览器提前解析和建立与某些域名的连接的技术,它可以减少DNS查询和TCP握手的时间,从而加快资源的加载速度。例如,<link rel="dns-prefetch" href="//example.com">表示浏览器提前解析example.com域名的IP地址,<link rel="preconnect" href="//example.com">表示浏览器提前建立与example.com域名的TCP连接。

(2) 稳定性

稳定性是指网络服务或应用在不同情况下能够正常运行和响应用户请求的能力,它涉及到网络服务或应用的可用性、可靠性、容错性等方面。稳定性是网络通信的重要目标之一,它可以影响用户的信任和满意度。为了提高稳定性,可以使用以下方法:

  • 重试机制:重试机制是指当网络请求发生错误或超时时,自动或手动地重新发送请求的技术,它可以增加请求成功的概率,并避免用户放弃操作。重试机制需要考虑重试次数、重试间隔、重试策略等因素,以避免造成过多的网络开销或无效的请求。

  • 缓存:缓存是一种存储机制,用于提高网络传输的效率和性能。缓存可以分为客户端缓存和服务器缓存两种。客户端缓存是指浏览器或其他应用程序在本地存储一些已经请求过的资源,以便下次访问时无需再向服务器请求;服务器缓存是指服务器在内存或硬盘中存储一些经常被请求的资源,以便快速响应客户端的请求。缓存不仅可以提高用户体验,还可以提高稳定性,因为当服务器出现故障或无法访问时,客户端仍然可以使用本地缓存来显示页面或功能。

  • 数据安全:数据安全是指保护网络服务或应用中的数据不被泄露、篡改或丢失的技术,它涉及到数据的加密、备份、恢复等方面。数据安全是网络通信的重要目标之一,它可以保护用户和服务提供者的利益和隐私。为了提高数据安全,可以使用以下方法:

    • HTTPS协议:HTTPS协议是HTTP协议的安全版本,它在HTTP协议的基础上加入了SSL/TLS层,用于实现数据的加密、认证和完整性保护。HTTPS协议可以防止数据在传输过程中被窃取或篡改,从而保证数据的机密性和完整性。
    • 数据备份:数据备份是指将网络服务或应用中的数据定期或实时地复制到其他存储设备或位置的技术,它可以防止数据因为硬件故障、软件错误、人为操作等原因而丢失,从而保证数据的可恢复性。
    • 数据恢复:数据恢复是指从备份设备或位置将数据恢复到原始设备或位置的技术,它可以在数据丢失后重新获取数据,从而保证数据的可用性。

五、了解更多网络通信协议相关知识

除了HTTP协议外,还有一些其他的网络通信协议值得了解,例如WebSocket协议和QUIC协议。这些协议有各自的特点和应用场景,可以与HTTP协议相互补充和优化。

1. WebSocket

WebSocket是一种基于TCP的双向通信协议,它可以实现客户端和服务器之间的实时、全双工、低延迟的数据交换。WebSocket协议与HTTP协议有以下区别:

  • WebSocket协议使用ws或wss作为URL的方案名,分别表示不加密或加密的连接,例如ws://example.com/chat表示一个不加密的WebSocket连接,wss://example.com/chat表示一个加密的WebSocket连接。
  • WebSocket协议使用一个握手请求来建立连接,该请求使用HTTP/1.1协议,并包含一些特殊的头部字段,例如Upgrade、Connection、Sec-WebSocket-Key等。服务器收到握手请求后,如果同意建立连接,则返回一个握手响应,该响应也使用HTTP/1.1协议,并包含一些特殊的头部字段,例如Upgrade、Connection、Sec-WebSocket-Accept等。握手完成后,客户端和服务器之间就建立了一个持久的TCP连接,并可以互相发送二进制或文本格式的数据帧。
  • WebSocket协议不需要使用请求-响应模式来交换数据,而是可以随时主动发送或接收数据。WebSocket协议也不需要使用头部字段来描述数据的属性和参数,而是使用一个固定长度的帧头来标识数据帧的类型、长度、掩码等信息。

WebSocket协议的优点是提高了网络通信的实时性和效率。WebSocket协议适合用于需要频繁、双向、低延迟的通信场景,例如聊天、游戏、股票行情等。

下面是一个前后端使用websocket协议进行通信的例子,前端使用axios,后端使用Node.js的express框架创建服务器。

  • 前端发起请求:
ini 复制代码
js
复制代码
// 导入 axios 和 ws
const axios = require('axios');
const WebSocket = require('ws');

// 发get请求拿到token
axios.get('http://localhost:3000/token')
  .then(res => {
    const token = res.data.token;
    const ws = new WebSocket(`ws://localhost:3000/websocket?token=${token}`);
    ws.on('open', () => {
      ws.send('Hello from client');
    });
    ws.on('message', data => {
      console.log(data);
    });
    ws.on('error', error => {
      console.error(error);
    });
    ws.on('close', () => {
      console.log('Connection closed');
    });
  })
  .catch(error => {
    console.error(error);
  });
  • 后端(需要安装下面导入的依赖):
javascript 复制代码
js
复制代码
// 导入 express, ws, 和 query-string包
const express = require('express');
const WebSocket = require('ws');
const queryString = require('query-string');

// 创建实例
const app = express();
const server = app.listen(3000, () => {
  console.log('Server listening on port 3000');
});

// get请求拿token
app.get('/token', (req, res) => {
  // 很简陋的token,也可以使用json-web-token包进行处理
  const token = Math.random().toString(36).substr(2);
  res.json({ token });
});

// 创建服务器
const wss = new WebSocket.Server({ noServer: true, path: '/websocket' });
wss.on('connection', (ws, req) => {
  // 拿到query参数
  const query = queryString.parse(req.url.split('?')[1]);
  // 从query参数拿到token
  const token = query.token;
  // 验证token
  if (token) {
    // 连接打开时向客户端发消息
    ws.send('Hello from server');
    // 接收并打印从客户端拿到的信息
    ws.on('message', data => {
      console.log(data);
    });
    // 处理错误
    ws.on('error', error => {
      console.error(error);
    });
    // 关闭连接
    ws.on('close', () => {
      console.log('Connection closed');
    });
  } else {
    // 无效令牌,拒绝连接
    ws.close(1008, 'Invalid token');
  }
});

// 处理express服务器收到的websocket upgrade请求
server.on('upgrade', (req, socket, head) => {
  wss.handleUpgrade(req, socket, head, (ws) => {
    wss.emit('connection', ws, req);
  });
});

2. QUIC

QUIC(Quick UDP Internet Connections)是一种基于UDP的多路复用传输协议,它由Google开发并推广,旨在解决TCP和HTTP/2协议存在的一些问题,例如队头阻塞、握手延迟、拥塞控制等。QUIC协议与HTTP/2协议有以下区别:

  • QUIC协议使用UDP作为传输层协议,而HTTP/2协议使用TCP作为传输层协议。- 这意味着QUIC协议不需要依赖于TCP的握手、流控制、拥塞控制等机制,而是在应用层实现了自己的握手、流控制、拥塞控制等机制。这样可以减少握手延迟、避免队头阻塞、提高传输效率等。
  • QUIC协议使用一个连接ID来标识一个连接,而HTTP/2协议使用一个五元组(源IP、源端口、目的IP、目的端口、协议)来标识一个连接。这意味着QUIC协议可以在网络环境变化时保持连接的状态,而HTTP/2协议则可能因为网络环境变化而导致连接中断或重置。
  • QUIC协议在连接建立时就进行了加密和认证,而HTTP/2协议则需要在TCP连接建立后再进行SSL/TLS握手来进行加密和认证。这意味着QUIC协议可以提高安全性和隐私性,而HTTP/2协议则需要额外的时间和开销来进行安全通信。

QUIC协议的优点是提高了网络通信的速度和稳定性。QUIC协议适合用于需要高效、可靠、安全的通信场景,例如视频流、在线游戏、实时通信等。

下面是一个前后端的例子,和上面一样的,都是使用axios和express。

要使用QUIC和express在前端和后端进行网络通信,需要以下几个步骤:

  • 在前端,需要使用一个支持QUIC协议的浏览器,例如Chrome、Edge或Firefox。也需要在浏览器中启用QUIC协议的设置,例如在Chrome中,你可以在chrome://flags中启用"Experimental QUIC protocol"选项。当然还可以使用一些扩展或插件来为特定的网站或域名启用QUIC协议。
  • 在后端,需要使用express和node-quic包来创建一个支持QUIC协议的服务器。还需要一个有效的证书和密钥来进行HTTPS通信。
  • 在前端和后端之间通过QUIC协议进行握手、加密、认证、数据传输等操作,类似于HTTP/2协议,但更快更稳定。
  • 前端发起请求代码:
typescript 复制代码
js
复制代码
// 引入axios和ws
const axios = require('axios');
const WebSocket = require('ws');

// 使用axios发送一个GET请求到express服务器,获取一个用于认证的令牌
axios.get('https://localhost:443/token')
  .then(res => {
    // 从响应数据中获取令牌
    const token = res.data.token;
    // 使用ws包创建一个websocket客户端,并使用令牌作为查询参数连接到express服务器
    const ws = new WebSocket(`wss://localhost:443/websocket?token=${token}`);
    // 设置websocket客户端的事件监听器
    ws.on('open', () => {
      // 当连接打开时,向服务器发送一条消息
      ws.send('Hello from client');
    });
    ws.on('message', data => {
      // 接收并打印服务器发送的消息
      console.log(data);
    });
    ws.on('error', error => {
      // 处理错误
      console.error(error);
    });
    ws.on('close', () => {
      // 关闭连接
      console.log('Connection closed');
    });
  })
  .catch(error => {
    // 处理错误
    console.error(error);
  });
  • 后端处理请求代码:
javascript 复制代码
js
复制代码
// 引入express和node-quic
const express = require('express');
const quic = require('node-quic');

// 创建一个express应用
const app = express();

// 创建一个路由处理器,用于处理GET /
app.get('/', (req, res) => {
  // 发送"Hello World!"作为响应
  res.send('Hello World!');
});

// 创建一个quic服务器,并将它附加到express应用上
const server = quic.createServer({
  // 设置证书和密钥文件
  key: 'server.key',
  cert: 'server.crt'
}, app);

// 监听UDP端口443
server.listen(443, () => {
  console.log('Server listening on port 443');
});

上面的两个例子很简单,仅仅是为了大家更好的理解而提供的。

相关推荐
努力的小郑1 小时前
Canal 不难,难的是用好:从接入到治理
后端·mysql·性能优化
Victor3562 小时前
MongoDB(87)如何使用GridFS?
后端
Victor3562 小时前
MongoDB(88)如何进行数据迁移?
后端
小红的布丁2 小时前
单线程 Redis 的高性能之道
redis·后端
GetcharZp2 小时前
Go 语言只能写后端?这款 2D 游戏引擎刷新你的认知!
后端
宁瑶琴3 小时前
COBOL语言的云计算
开发语言·后端·golang
普通网友4 小时前
阿里云国际版服务器,真的是学生党的性价比之选吗?
后端·python·阿里云·flask·云计算
IT_陈寒5 小时前
Vue的这个响应式问题,坑了我整整两小时
前端·人工智能·后端
Soofjan5 小时前
Go 内存回收-GC 源码1-触发与阶段
后端
shining5 小时前
[Golang]Eino探索之旅-初窥门径
后端