MK
摩柯社区 - 一个极简的技术知识社区
AI 面试

HTTP协议的工作原理与请求方法

2024-10-207.8k 阅读

HTTP 协议基础

HTTP 协议简介

HTTP(Hyper - Text Transfer Protocol)即超文本传输协议,它是一种用于分布式、协作式和超媒体信息系统的应用层协议 ,在万维网(WWW)的数据通信中扮演着核心角色。HTTP 协议是基于 TCP/IP 协议栈之上的,它规定了客户端和服务器之间如何进行请求与响应的交互,使得我们能够在浏览器中浏览网页、获取资源等。

HTTP 协议从 1991 年的 0.9 版本开始发展,历经了 1.0、1.1 等重要版本,目前 HTTP/2 和 HTTP/3 也已逐渐得到广泛应用。不同版本在性能、功能等方面有着不同的改进和特点,但它们的核心交互模式依然保持着相似性。

HTTP 协议的特点

  1. 无状态:HTTP 协议是无状态的,这意味着服务器不会在不同请求之间保留关于客户端的任何信息。每次请求都是独立的,服务器无法从之前的请求中获取上下文。例如,一个用户在浏览器中打开多个网页,每个网页的请求对于服务器来说都是全新的,服务器不会记得该用户之前访问过哪些页面。这种无状态特性使得 HTTP 协议简单且易于实现,同时也提高了服务器的可扩展性,因为服务器无需为每个客户端维护状态信息。然而,在一些需要跟踪用户会话的场景下,如用户登录后访问多个需要权限的页面,就需要借助其他技术手段(如 cookie、session 等)来实现有状态的效果。
  2. 基于请求 - 响应模型:客户端发起请求,服务器接收到请求后进行处理并返回响应。客户端和服务器之间通过这种一问一答的方式进行通信。例如,当我们在浏览器中输入一个网址并回车,浏览器就作为客户端向服务器发送一个 HTTP 请求,服务器根据请求的内容找到对应的资源,并将该资源以 HTTP 响应的形式返回给浏览器。这种模型使得 HTTP 通信清晰明了,易于理解和开发。
  3. 应用层协议:HTTP 协议位于 TCP/IP 协议栈的应用层,它依赖于传输层的 TCP 协议来保证数据的可靠传输。TCP 协议负责将 HTTP 数据分割成合适的数据包,并在网络中进行传输,同时确保数据的顺序性和完整性。HTTP 协议专注于定义如何构建请求和响应的格式,以及规定客户端和服务器之间交互的规则。

HTTP 协议的工作原理

客户端发起请求

  1. URL 解析:当用户在浏览器地址栏输入一个 URL(Uniform Resource Locator,统一资源定位符)或者点击一个链接时,浏览器首先会对 URL 进行解析。例如,对于 URL “http://www.example.com:8080/path/to/resource?query=value”,浏览器会解析出协议(http)、主机名(www.example.com)、端口号(8080,如果未指定,HTTP 默认为 80,HTTPS 默认为 443)、路径(/path/to/resource)和查询参数(query=value)。
  2. 建立 TCP 连接:浏览器在解析 URL 得到主机名和端口号后,会通过 DNS(Domain Name System,域名系统)将主机名解析为对应的 IP 地址。然后,浏览器使用 TCP 协议与服务器建立连接。TCP 连接的建立过程遵循三次握手,即客户端发送一个 SYN 包到服务器,服务器收到后返回一个 SYN + ACK 包,客户端再发送一个 ACK 包,这样就完成了连接的建立。
  3. 构建 HTTP 请求:一旦 TCP 连接建立成功,浏览器就开始构建 HTTP 请求。HTTP 请求由三部分组成:请求行、请求头和请求体(对于 GET 请求,请求体通常为空)。
    • 请求行:包含请求方法(如 GET、POST 等)、请求的资源路径和使用的 HTTP 版本。例如,“GET /index.html HTTP/1.1”,表示使用 GET 方法请求服务器上的 /index.html 资源,使用的 HTTP 版本是 1.1。
    • 请求头:包含了关于请求的一些元信息,如客户端的信息(User - Agent)、接受的数据类型(Accept)、是否接受压缩(Accept - Encoding)等。例如,“User - Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/91.0.4472.124 Safari/537.36”表示客户端是 Chrome 浏览器,运行在 Windows 10 64 位系统上。
    • 请求体:对于 POST、PUT 等需要向服务器提交数据的请求方法,请求体中包含了要提交的数据。例如,在一个登录表单中,用户名和密码会被放在请求体中以特定的格式(如 application/x - www - form - urlencoded)发送给服务器。

服务器处理请求

  1. 接收请求:服务器在指定的端口监听 TCP 连接,当客户端的连接请求到达时,服务器接受连接并接收 HTTP 请求。服务器会从 TCP 连接中读取数据,并按照 HTTP 协议的格式进行解析,获取请求行、请求头和请求体的内容。
  2. 解析请求:服务器根据请求行中的请求方法和请求路径来确定如何处理请求。例如,如果是 GET 请求,服务器通常会查找请求路径对应的资源文件(如 HTML、CSS、JavaScript 文件等);如果是 POST 请求,服务器会从请求体中获取提交的数据,并根据业务逻辑进行处理,如将数据插入数据库等。
  3. 生成响应:服务器处理完请求后,会生成 HTTP 响应。HTTP 响应同样由三部分组成:状态行、响应头和响应体。
    • 状态行:包含 HTTP 版本、状态码和状态描述。例如,“HTTP/1.1 200 OK”表示使用 HTTP 1.1 版本,状态码为 200,说明请求成功,状态描述为“OK”。常见的状态码有 200(成功)、404(未找到资源)、500(服务器内部错误)等。
    • 响应头:包含了关于响应的一些元信息,如响应的数据类型(Content - Type)、数据长度(Content - Length)、缓存控制(Cache - Control)等。例如,“Content - Type: text/html; charset=UTF - 8”表示响应的数据类型是 HTML,字符编码为 UTF - 8。
    • 响应体:包含了服务器返回给客户端的实际数据,如 HTML 页面内容、JSON 格式的数据等。如果请求的资源是一个 HTML 页面,那么响应体中就会包含该 HTML 页面的代码。

客户端接收响应

  1. 接收响应数据:客户端在建立的 TCP 连接上接收服务器返回的 HTTP 响应。客户端会按照 HTTP 协议的格式解析响应,首先读取状态行,判断请求是否成功。如果状态码是 200,说明请求成功,客户端继续读取响应头和响应体。
  2. 处理响应:客户端根据响应头中的信息来处理响应体。例如,如果响应头中“Content - Type”为“text/html”,浏览器会将响应体中的内容解析为 HTML 页面,并进行渲染显示;如果“Content - Type”为“application/json”,客户端可能会将响应体中的 JSON 数据解析为对象,以便在 JavaScript 代码中进一步处理。

HTTP 连接的关闭

  1. 短连接:在 HTTP/1.0 中,默认使用短连接。当客户端和服务器完成一次请求 - 响应交互后,TCP 连接就会被关闭。如果客户端需要再次请求资源,就需要重新建立 TCP 连接。这种方式虽然简单,但每次建立和关闭连接都会带来额外的开销,在频繁请求的场景下效率较低。
  2. 长连接:HTTP/1.1 引入了长连接机制,通过在请求头或响应头中设置“Connection: keep - alive”字段来启用长连接。在长连接模式下,TCP 连接在一次请求 - 响应完成后不会立即关闭,客户端可以在同一个连接上发送多个请求,服务器也可以在该连接上依次返回响应。这样可以减少连接建立和关闭的开销,提高性能。然而,长连接也需要注意资源的管理,因为长时间保持连接可能会占用服务器资源。

HTTP 请求方法

GET 方法

  1. 用途:GET 方法是最常用的 HTTP 请求方法之一,主要用于从服务器获取资源。例如,当我们在浏览器中输入网址访问一个网页时,浏览器默认使用 GET 方法向服务器请求该网页的资源。GET 方法将请求的参数附加在 URL 的查询字符串中,服务器通过解析查询字符串来获取参数值。
  2. 特点
    • 幂等性:GET 方法具有幂等性,即多次执行相同的 GET 请求,对服务器资源的影响是相同的。例如,多次请求“http://www.example.com/products?category=electronics”,服务器返回的结果应该是相同的,且不会对服务器上的产品数据造成额外的修改。
    • 安全性:从安全性角度来看,GET 方法相对不安全,因为请求参数暴露在 URL 中,可能会被他人截取。例如,在 URL 中包含用户名和密码的 GET 请求是非常危险的,因为这些信息可能会被记录在浏览器历史记录、服务器日志等地方,容易导致信息泄露。
    • 数据长度限制:由于 URL 的长度限制(不同浏览器和服务器可能有不同的限制,一般在 2000 个字符左右),GET 方法能传递的数据量有限。如果需要传递大量数据,不适合使用 GET 方法。
  3. 代码示例(Python Flask)
from flask import Flask, request

app = Flask(__name__)


@app.route('/get_data', methods=['GET'])
def get_data():
    name = request.args.get('name')
    age = request.args.get('age')
    return f"Name: {name}, Age: {age}"


if __name__ == '__main__':
    app.run(debug=True)

在上述代码中,Flask 应用定义了一个路由“/get_data”,接受 GET 请求。通过request.args.get方法获取 URL 中的查询参数“name”和“age”,并返回包含这些参数值的响应。

POST 方法

  1. 用途:POST 方法主要用于向服务器提交数据,通常用于创建新资源的场景。例如,用户在注册表单中填写信息并提交,浏览器会使用 POST 方法将用户填写的数据发送到服务器,服务器可以将这些数据保存到数据库中。
  2. 特点
    • 非幂等性:POST 方法是非幂等的,多次执行相同的 POST 请求可能会在服务器上创建多个相同的资源。例如,多次提交注册表单可能会在数据库中创建多个相同的用户记录。
    • 安全性:相对于 GET 方法,POST 方法的安全性较高,因为数据是放在请求体中,不会暴露在 URL 中。但这并不意味着 POST 方法绝对安全,如果传输过程没有加密(如使用 HTTP 而不是 HTTPS),数据依然可能被截取。
    • 数据长度无限制:理论上,POST 方法对数据长度没有像 GET 方法那样严格的限制,因为数据是放在请求体中,而不是 URL 中。但实际应用中,服务器和网络环境可能会对请求体的大小有一定限制。
  3. 代码示例(Python Flask)
from flask import Flask, request

app = Flask(__name__)


@app.route('/post_data', methods=['POST'])
def post_data():
    name = request.form.get('name')
    age = request.form.get('age')
    return f"Name: {name}, Age: {age}"


if __name__ == '__main__':
    app.run(debug=True)

在这个 Flask 应用中,定义了一个路由“/post_data”,接受 POST 请求。通过request.form.get方法获取表单数据中的“name”和“age”字段,并返回相应的响应。

PUT 方法

  1. 用途:PUT 方法用于更新服务器上的资源。它通常用于完整地替换资源,即客户端将整个资源的最新状态发送到服务器,服务器根据请求中的数据更新对应的资源。例如,如果有一个用户信息资源,客户端可以使用 PUT 方法将修改后的用户信息发送到服务器,服务器用新的数据替换原有的用户信息。
  2. 特点
    • 幂等性:PUT 方法具有幂等性,多次执行相同的 PUT 请求,对服务器资源的最终状态应该是相同的。例如,多次使用相同的数据执行 PUT 请求更新一个文件内容,文件的最终内容应该是一样的。
    • 与 POST 的区别:与 POST 方法不同,PUT 方法更强调对资源的完整替换,而 POST 方法通常用于创建新资源或部分更新资源。另外,PUT 方法的请求 URL 应该指向具体的资源,而 POST 方法的 URL 可能指向一个集合资源,由服务器决定如何处理提交的数据。
  3. 代码示例(Python Flask)
from flask import Flask, request

app = Flask(__name__)

# 模拟数据存储
data_store = {}


@app.route('/put_data/<id>', methods=['PUT'])
def put_data(id):
    data = request.get_json()
    data_store[id] = data
    return f"Resource with ID {id} updated successfully"


if __name__ == '__main__':
    app.run(debug=True)

在上述代码中,Flask 应用定义了一个路由“/put_data/”,接受 PUT 请求。通过request.get_json方法获取请求体中的 JSON 数据,并将其存储在模拟的数据存储data_store中,以模拟更新资源的操作。

DELETE 方法

  1. 用途:DELETE 方法用于删除服务器上的资源。客户端使用 DELETE 方法发送请求到服务器,服务器根据请求 URL 中指定的资源路径删除对应的资源。例如,在一个文件管理系统中,用户可以使用 DELETE 方法删除指定的文件。
  2. 特点
    • 幂等性:DELETE 方法具有幂等性,多次执行相同的 DELETE 请求,对服务器资源的最终状态应该是相同的(即资源被删除)。即使多次尝试删除一个已经不存在的资源,也不会对服务器造成额外的不良影响。
    • 安全性:由于 DELETE 方法会直接删除资源,所以在使用时需要谨慎,确保请求的合法性和安全性。通常需要进行身份验证和授权,以防止非法删除操作。
  3. 代码示例(Python Flask)
from flask import Flask, request

app = Flask(__name__)

# 模拟数据存储
data_store = {'1': 'example data'}


@app.route('/delete_data/<id>', methods=['DELETE'])
def delete_data(id):
    if id in data_store:
        del data_store[id]
        return f"Resource with ID {id} deleted successfully"
    else:
        return "Resource not found", 404


if __name__ == '__main__':
    app.run(debug=True)

在这个 Flask 应用中,定义了一个路由“/delete_data/”,接受 DELETE 请求。如果指定 ID 的资源存在于模拟的数据存储data_store中,则将其删除并返回成功消息;如果资源不存在,则返回 404 状态码和相应的错误消息。

HEAD 方法

  1. 用途:HEAD 方法与 GET 方法类似,也是用于获取资源的信息,但它只返回响应头,不返回响应体。这种方法常用于检查资源的状态、获取资源的元信息(如文件大小、修改时间等),而不需要获取整个资源的内容。例如,在下载一个大文件之前,可以先使用 HEAD 方法获取文件的大小,以判断是否有足够的空间下载。
  2. 特点:由于 HEAD 方法不返回响应体,所以它的响应速度通常比 GET 方法快,因为减少了数据传输量。同时,它的行为和 GET 方法在其他方面基本相同,如请求头的处理等。
  3. 代码示例(Python Flask)
from flask import Flask, request, make_response

app = Flask(__name__)


@app.route('/head_data', methods=['HEAD'])
def head_data():
    response = make_response()
    response.headers['Content - Type'] = 'text/plain'
    response.headers['Content - Length'] = '100'
    return response


if __name__ == '__main__':
    app.run(debug=True)

在上述代码中,Flask 应用定义了一个路由“/head_data”,接受 HEAD 请求。通过make_response创建一个响应对象,并设置了“Content - Type”和“Content - Length”等响应头字段,模拟返回资源的元信息。

OPTIONS 方法

  1. 用途:OPTIONS 方法用于获取服务器支持的 HTTP 方法和其他相关信息。客户端发送 OPTIONS 请求到服务器,服务器返回的响应头中会包含允许的 HTTP 方法(如“Allow: GET, POST, PUT, DELETE”)等信息。这个方法在跨域资源共享(CORS)中起着重要作用,浏览器在发送实际的跨域请求之前,通常会先发送一个 OPTIONS 预检请求,以检查服务器是否允许该跨域请求。
  2. 特点:OPTIONS 方法是一种预检查机制,它不会对服务器资源造成任何修改。服务器通过响应头中的信息告知客户端可以使用哪些方法以及其他相关的限制或配置。
  3. 代码示例(Python Flask)
from flask import Flask, request, make_response

app = Flask(__name__)


@app.route('/options_data', methods=['OPTIONS'])
def options_data():
    response = make_response()
    response.headers['Allow'] = 'GET, POST, PUT, DELETE'
    return response


if __name__ == '__main__':
    app.run(debug=True)

在这个 Flask 应用中,定义了一个路由“/options_data”,接受 OPTIONS 请求。通过make_response创建响应对象,并设置“Allow”响应头字段,告知客户端该资源支持的 HTTP 方法。

PATCH 方法

  1. 用途:PATCH 方法用于对服务器上的资源进行部分更新。与 PUT 方法不同,PUT 方法通常用于完整替换资源,而 PATCH 方法只需要发送需要更新的部分数据。例如,当只需要修改用户信息中的某个字段(如电话号码)时,可以使用 PATCH 方法发送包含新电话号码的部分数据,服务器根据这些数据对用户信息进行部分更新。
  2. 特点:PATCH 方法的灵活性较高,适用于只需要更新资源部分内容的场景。但它不是幂等的,因为多次执行相同的 PATCH 请求可能会因为每次请求的部分数据不同而导致不同的结果。
  3. 代码示例(Python Flask)
from flask import Flask, request

app = Flask(__name__)

# 模拟数据存储
data_store = {'1': {'name': 'John', 'age': 30}}


@app.route('/patch_data/<id>', methods=['PATCH'])
def patch_data(id):
    if id in data_store:
        data = request.get_json()
        for key, value in data.items():
            data_store[id][key] = value
        return f"Resource with ID {id} partially updated successfully"
    else:
        return "Resource not found", 404


if __name__ == '__main__':
    app.run(debug=True)

在上述代码中,Flask 应用定义了一个路由“/patch_data/”,接受 PATCH 请求。如果指定 ID 的资源存在于模拟的数据存储data_store中,通过request.get_json获取请求体中的 JSON 数据,并对资源进行部分更新;如果资源不存在,则返回 404 状态码和相应的错误消息。