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

HTTP协议在Web开发中的应用实践

2021-11-026.6k 阅读

HTTP 协议基础

HTTP 协议概述

HTTP(HyperText Transfer Protocol)即超文本传输协议,它是用于在万维网(WWW)上进行数据传输的应用层协议。HTTP 诞生于 1989 年,最初版本是 HTTP/0.9,经过不断发展,目前广泛使用的是 HTTP/1.1,同时 HTTP/2 和 HTTP/3 也逐渐被应用。

HTTP 协议是基于客户端 - 服务器模型的。客户端通常是浏览器,向服务器发送请求,服务器处理请求并返回响应。例如,当用户在浏览器中输入一个网址并回车,浏览器就作为客户端向对应的服务器发送 HTTP 请求,服务器解析请求,找到相应的资源(如网页、图片、视频等),然后将其封装成 HTTP 响应返回给浏览器。

HTTP 请求

  1. 请求方法 HTTP 定义了多种请求方法,每种方法表示对资源的不同操作。常见的请求方法有:
  • GET:用于获取资源,通常将请求参数附加在 URL 后面。例如,https://example.com/api/users?id=1,这里 id=1 就是通过 GET 方法传递的参数。GET 请求的参数会显示在 URL 中,长度有限制,并且不适合传输敏感信息。
  • POST:用于提交数据到服务器,数据包含在请求体中。常用于表单提交、文件上传等场景。例如,用户注册时填写的用户名、密码等信息,通过 POST 请求发送到服务器,这些信息不会显示在 URL 中,相对 GET 更安全,且可传输的数据量更大。
  • PUT:用于更新资源。如果资源不存在,有些服务器会创建该资源。例如,要更新用户的邮箱地址,可以向服务器发送 PUT 请求,请求体中包含新的邮箱信息。
  • DELETE:用于删除资源。比如,删除数据库中的某条记录,可以发送 DELETE 请求到对应的资源路径。
  1. 请求头 请求头包含了关于请求的附加信息,例如客户端的信息、请求的类型、缓存控制等。以下是一些常见的请求头字段:
  • 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,通过这个字段服务器可以了解请求来自哪种浏览器及操作系统。
  • Content - Type:表示请求体的数据类型。常见的值有 application/json(表示 JSON 格式的数据)、application/x - www - form - urlencoded(用于表单数据,格式为 key1=value1&key2=value2)、multipart/form - data(用于文件上传等多部分数据)。
  • Accept:告诉服务器客户端能够接受的响应数据类型。例如,Accept: application/json 表示客户端希望服务器返回 JSON 格式的数据。
  1. 请求体 请求体包含了客户端发送给服务器的实际数据,仅在 POST、PUT 等部分请求方法中存在。如前面提到的,当使用 POST 方法提交表单数据时,表单中的字段和值就包含在请求体中。如果是 JSON 格式的数据,请求体可能如下:
{
    "username": "testuser",
    "password": "testpass"
}

HTTP 响应

  1. 响应状态码 响应状态码表示服务器对请求的处理结果。状态码由三位数字组成,第一个数字定义了响应的类别,常见的类别有:
  • 1xx(信息性状态码):表示请求已被接收,继续处理。例如,100 Continue 表示客户端可以继续发送请求的剩余部分。
  • 2xx(成功状态码):表示请求成功处理。其中,200 OK 是最常见的,表示请求成功,服务器已成功返回所请求的资源。201 Created 表示请求已成功,并且服务器创建了新的资源,常用于 POST 请求创建新资源后返回。
  • 3xx(重定向状态码):表示需要客户端采取进一步的操作来完成请求。例如,301 Moved Permanently 表示资源已永久移动,客户端应使用新的 URL 重新请求;302 Found 表示资源临时移动,客户端应使用原 URL 再次请求。
  • 4xx(客户端错误状态码):表示客户端发送的请求有错误。例如,400 Bad Request 表示客户端请求的语法错误,服务器无法理解;401 Unauthorized 表示请求要求用户身份验证,若未提供有效的认证信息则返回此状态码;404 Not Found 表示服务器无法找到请求的资源。
  • 5xx(服务器错误状态码):表示服务器在处理请求时发生错误。例如,500 Internal Server Error 表示服务器内部发生错误,无法完成请求;503 Service Unavailable 表示服务器暂时不可用,通常是由于服务器过载或正在维护。
  1. 响应头 响应头包含了关于响应的附加信息,例如服务器的类型、响应数据的缓存控制等。常见的响应头字段有:
  • Server:标识服务器的类型和版本。例如,Server: Apache/2.4.46 表示使用的是 Apache 服务器,版本为 2.4.46。
  • Content - Type:表示响应体的数据类型,与请求头中的 Content - Type 类似,用于告知客户端如何解析响应数据。
  • Cache - Control:用于控制缓存策略。例如,Cache - Control: no - cache 表示不允许缓存,每次请求都要从服务器获取最新数据;Cache - Control: max - age = 3600 表示缓存有效期为 3600 秒,在有效期内客户端可以直接使用缓存数据。
  1. 响应体 响应体包含了服务器返回给客户端的实际数据,例如请求的网页内容、API 返回的 JSON 数据等。如果请求的是一个 HTML 网页,响应体可能就是整个 HTML 文档的内容:
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF - 8">
    <title>Example Page</title>
</head>
<body>
    <h1>Welcome to the Example Page</h1>
    <p>This is some sample content.</p>
</body>
</html>

HTTP 在 Web 开发中的应用场景

网页浏览

在网页浏览过程中,HTTP 协议起着核心作用。当用户在浏览器中输入网址并按下回车键后,浏览器会构建 HTTP 请求并发送到对应的服务器。服务器收到请求后,根据请求的 URL 找到相应的网页资源,然后将网页内容封装成 HTTP 响应返回给浏览器。例如,当用户访问 https://www.baidu.com,浏览器发送 GET 请求到百度服务器,服务器返回百度首页的 HTML 内容,浏览器解析 HTML 并渲染出网页。

在这个过程中,浏览器可能还会发送多个 HTTP 请求来获取网页中的其他资源,如 CSS 文件、JavaScript 文件、图片等。例如,HTML 中引用了一个 CSS 文件:

<link rel="stylesheet" href="styles.css">

浏览器会发送一个 GET 请求获取 styles.css 文件,以正确渲染网页的样式。同样,对于 JavaScript 文件和图片也会有类似的请求过程。

API 开发与调用

  1. RESTful API REST(Representational State Transfer)是一种设计 Web 服务的架构风格,基于 HTTP 协议。RESTful API 是遵循 REST 原则设计的 API。在 RESTful API 中,每个资源都有一个唯一的 URL 标识,通过 HTTP 的不同请求方法对资源进行操作。

例如,假设我们有一个用户管理的 RESTful API:

  • 获取所有用户:GET /api/users
  • 获取单个用户:GET /api/users/{id},其中 {id} 是用户的唯一标识符
  • 创建新用户:POST /api/users,请求体中包含新用户的信息
  • 更新用户信息:PUT /api/users/{id},请求体中包含更新后的用户信息
  • 删除用户:DELETE /api/users/{id}

下面是一个简单的 Python Flask 框架实现的 RESTful API 示例:

from flask import Flask, jsonify, request

app = Flask(__name__)

# 模拟用户数据
users = [
    {"id": 1, "name": "Alice"},
    {"id": 2, "name": "Bob"}
]

@app.route('/api/users', methods=['GET'])
def get_users():
    return jsonify(users)

@app.route('/api/users/<int:id>', methods=['GET'])
def get_user(id):
    user = next((user for user in users if user['id'] == id), None)
    if user is None:
        return jsonify({"message": "User not found"}), 404
    return jsonify(user)

@app.route('/api/users', methods=['POST'])
def create_user():
    data = request.get_json()
    new_user = {"id": len(users) + 1, "name": data.get('name')}
    users.append(new_user)
    return jsonify(new_user), 201

@app.route('/api/users/<int:id>', methods=['PUT'])
def update_user(id):
    user = next((user for user in users if user['id'] == id), None)
    if user is None:
        return jsonify({"message": "User not found"}), 404
    data = request.get_json()
    user['name'] = data.get('name', user['name'])
    return jsonify(user)

@app.route('/api/users/<int:id>', methods=['DELETE'])
def delete_user(id):
    user = next((user for user in users if user['id'] == id), None)
    if user is None:
        return jsonify({"message": "User not found"}), 404
    users.remove(user)
    return jsonify({"message": "User deleted"})

if __name__ == '__main__':
    app.run(debug=True)
  1. GraphQL API GraphQL 是一种用于 API 的查询语言,也是一个满足这些查询的运行时。与 RESTful API 不同,GraphQL 允许客户端精确指定它需要的数据,避免了过度获取或获取不足数据的问题。GraphQL 同样基于 HTTP 协议,通常通过 POST 请求发送查询。

例如,假设我们有一个博客的 GraphQL API,客户端可以发送如下查询获取一篇文章及其作者信息:

query {
    article(id: 1) {
        title
        content
        author {
            name
        }
    }
}

服务器接收到这个查询后,解析并返回相应的数据。下面是一个简单的 Python Flask - GraphQL 实现的示例:

from flask import Flask
from flask_graphql import GraphQLView
from graphene import ObjectType, String, Int, Schema

class Article(ObjectType):
    title = String()
    content = String()
    author = String()

class Query(ObjectType):
    article = Article(id = Int())
    def resolve_article(self, info, id):
        # 模拟数据获取
        if id == 1:
            return Article(title='Sample Article', content='This is the content', author='John')
        return None

schema = Schema(query = Query)

app = Flask(__name__)
app.add_url_rule('/graphql', view_func=GraphQLView.as_view('graphql', schema = schema, graphiql = True))

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

身份验证与授权

  1. 基本认证(Basic Authentication) 基本认证是一种简单的身份验证方式,客户端在请求头中发送用户名和密码的 Base64 编码。例如,用户名 admin 和密码 password,经过 Base64 编码后为 YWRtaW46cGFzc3dvcmQ=,在请求头中添加 Authorization: Basic YWRtaW46cGFzc3dvcmQ=。服务器接收到请求后,解码 Base64 字符串,验证用户名和密码。

以下是一个简单的 Python Flask 示例,演示基本认证:

from flask import Flask, request, jsonify
from werkzeug.security import check_password_hash
from base64 import b64decode

app = Flask(__name__)

# 模拟用户数据
users = {
    "admin": "$pbkdf2 - hmac - sha256$29000$59QK6X3DfJ0U1KZ5.$84963c96c86d85d58746f4686666c475565c455c8556568766d85d58746f468"
}

@app.route('/protected', methods=['GET'])
def protected():
    auth_header = request.headers.get('Authorization')
    if not auth_header or not auth_header.startswith('Basic '):
        return jsonify({"message": "Authorization header is missing or invalid"}), 401
    encoded_credentials = auth_header.split(' ')[1]
    decoded_credentials = b64decode(encoded_credentials).decode('utf - 8')
    username, password = decoded_credentials.split(':', 1)
    stored_hash = users.get(username)
    if not stored_hash or not check_password_hash(stored_hash, password):
        return jsonify({"message": "Invalid username or password"}), 401
    return jsonify({"message": "Access granted"})

if __name__ == '__main__':
    app.run(debug = True)
  1. Bearer Token 认证 Bearer Token 认证是目前较为流行的方式,客户端在请求头中发送一个令牌(token),服务器验证该令牌的有效性。例如,在请求头中添加 Authorization: Bearer <token>。令牌通常是 JWT(JSON Web Token),它包含了用户的身份信息、权限信息等。服务器接收到请求后,验证 JWT 的签名和有效期等信息。

以下是一个简单的 Python Flask 示例,使用 PyJWT 库实现 JWT 认证:

from flask import Flask, request, jsonify
import jwt
from datetime import datetime, timedelta
from functools import wraps

app = Flask(__name__)
app.config['SECRET_KEY'] = 'your_secret_key'

def token_required(f):
    @wraps(f)
    def decorated(*args, **kwargs):
        token = None
        if 'Authorization' in request.headers:
            token = request.headers['Authorization'].split(' ')[1]
        if not token:
            return jsonify({"message": "Token is missing!"}), 401
        try:
            data = jwt.decode(token, app.config['SECRET_KEY'], algorithms=['HS256'])
        except jwt.ExpiredSignatureError:
            return jsonify({"message": "Token has expired!"}), 401
        except jwt.InvalidTokenError:
            return jsonify({"message": "Invalid token!"}), 401
        return f(*args, **kwargs)
    return decorated

@app.route('/login')
def login():
    auth = request.authorization
    if not auth or not auth.username or not auth.password:
        return jsonify({"message": "Could not verify"}), 401
    if auth.username == 'admin' and auth.password == 'password':
        token = jwt.encode({
            'user': auth.username,
            'exp': datetime.utcnow() + timedelta(minutes = 30)
        }, app.config['SECRET_KEY'], algorithm='HS256')
        return jsonify({"token": token})
    return jsonify({"message": "Could not verify"}), 401

@app.route('/protected', methods=['GET'])
@token_required
def protected():
    return jsonify({"message": "This is a protected route"})

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

HTTP 协议优化与性能提升

缓存策略优化

  1. 浏览器缓存 浏览器缓存可以减少对服务器的请求,提高网页加载速度。浏览器缓存分为强缓存和协商缓存。
  • 强缓存:当浏览器请求资源时,首先检查强缓存。如果资源在缓存中且未过期,浏览器直接从缓存中获取资源,不会向服务器发送请求。强缓存通过 Cache - ControlExpires 响应头控制。例如,Cache - Control: max - age = 3600 表示资源在 3600 秒内有效,在此期间浏览器会直接使用缓存资源。Expires 字段是一个绝对时间,例如 Expires: Thu, 01 Dec 2022 16:00:00 GMT,表示资源在该时间之前有效。
  • 协商缓存:如果强缓存失效,浏览器会发起请求到服务器进行协商缓存。服务器通过 Last - ModifiedETag 响应头与浏览器协作。Last - Modified 表示资源的最后修改时间,浏览器在后续请求中通过 If - Modified - Since 请求头发送该时间,服务器比较资源的最后修改时间与 If - Modified - Since 的值,如果未修改,返回 304 Not Modified,浏览器从缓存中加载资源;否则返回最新资源。ETag 是资源的唯一标识符,类似文件的指纹,服务器通过 ETag 响应头返回,浏览器在后续请求中通过 If - None - Match 请求头发送,服务器比较 ETag 值,若相同返回 304 Not Modified。
  1. CDN 缓存 CDN(Content Delivery Network)即内容分发网络,它通过在多个地理位置部署服务器节点,缓存和分发内容。当用户请求资源时,CDN 服务器会根据用户的地理位置选择距离最近的节点提供服务。例如,当用户请求一张图片时,如果该图片已经缓存在距离用户较近的 CDN 节点,就可以直接从该节点获取,而不需要从源服务器获取,大大提高了资源的加载速度。

压缩传输

  1. Gzip 压缩 Gzip 是一种广泛使用的文件压缩算法,HTTP 协议支持使用 Gzip 对响应数据进行压缩,以减少数据传输量。服务器在响应头中添加 Content - Encoding: gzip 表示数据使用了 Gzip 压缩。浏览器接收到压缩数据后,自动解压缩。

在 Python Flask 中启用 Gzip 压缩很简单,安装 flask - compress 库后,配置如下:

from flask import Flask
from flask_compress import Compress

app = Flask(__name__)
Compress(app)

@app.route('/')
def index():
    return "This is a sample response"

if __name__ == '__main__':
    app.run(debug = True)
  1. Brotli 压缩 Brotli 是一种新的压缩算法,比 Gzip 有更好的压缩效果。与 Gzip 类似,服务器在响应头中添加 Content - Encoding: br 表示使用 Brotli 压缩。现代浏览器大多支持 Brotli 压缩。例如,在 Node.js 中使用 Express 框架启用 Brotli 压缩:
const express = require('express');
const brotli = require('iltorb');
const app = express();

app.use(async (req, res, next) => {
    if (req.headers['accept - encoding'] && req.headers['accept - encoding'].includes('br')) {
        res.set('Content - Encoding', 'br');
        const originalSend = res.send;
        res.send = function (body) {
            if (typeof body ==='string' || Buffer.isBuffer(body)) {
                const compressed = brotli.compressSync(body);
                return originalSend.call(this, compressed);
            }
            return originalSend.call(this, body);
        };
    }
    next();
});

app.get('/', (req, res) => {
    res.send('This is a sample response');
});

app.listen(3000, () => {
    console.log('Server is running on port 3000');
});

HTTP/2 与 HTTP/3 的优势

  1. HTTP/2 HTTP/2 相比 HTTP/1.1 有诸多改进:
  • 多路复用:HTTP/1.1 每个 TCP 连接只能处理一个请求 - 响应,而 HTTP/2 允许在一个连接上同时处理多个请求 - 响应,避免了队头阻塞问题,提高了传输效率。例如,一个网页有多个资源(图片、脚本等)需要请求,在 HTTP/1.1 下可能需要建立多个 TCP 连接依次请求,而 HTTP/2 可以在一个连接上并行请求这些资源。
  • 头部压缩:HTTP/2 使用 HPACK 算法对请求和响应头进行压缩,减少了头部数据的传输量。例如,在 HTTP/1.1 中,每次请求都可能携带大量重复的头部信息,而 HTTP/2 会对头部进行编码和压缩,降低传输成本。
  • 服务器推送:服务器可以主动将客户端可能需要的资源推送给客户端,提前缓存,提高用户体验。例如,服务器知道客户端请求一个 HTML 页面,同时可能需要相关的 CSS 和 JavaScript 文件,就可以主动推送给客户端。
  1. HTTP/3 HTTP/3 基于 UDP 协议,使用 QUIC(Quick UDP Internet Connections)协议。它的优势在于:
  • 更低的延迟:由于基于 UDP,避免了 TCP 连接建立的三次握手延迟,以及 TCP 重传机制带来的延迟。在网络不稳定的情况下,HTTP/3 能更快地恢复连接,提高传输效率。
  • 更好的连接迁移:当用户的网络环境发生变化(如从 Wi - Fi 切换到移动数据),HTTP/3 可以更轻松地迁移连接,而不需要重新建立连接,保证数据传输的连续性。

总结

HTTP 协议是 Web 开发的基石,深入理解其原理和应用对于后端开发者至关重要。从基本的请求 - 响应模型,到复杂的应用场景如 API 开发、身份验证,再到性能优化的缓存策略、压缩传输以及新协议的优势,HTTP 协议不断演进以满足日益增长的 Web 应用需求。通过合理运用 HTTP 协议的特性,开发者可以构建高效、稳定、安全的 Web 应用。在实际开发中,要根据具体的业务需求和场景,选择合适的 HTTP 技术和优化策略,为用户提供更好的体验。同时,随着技术的不断发展,持续关注 HTTP 协议的新进展,如 HTTP/3 的进一步普及,也是非常必要的。