阿里云主机折上折
  • 微信号
您当前的位置:网站首页 > HTTP 请求方法全面解析

HTTP 请求方法全面解析

作者:陈川 阅读数:63866人阅读 分类: Node.js

HTTP请求方法概述

HTTP协议定义了多种请求方法,用于表示对资源的不同操作类型。这些方法构成了RESTful架构的基础,决定了客户端与服务器交互的方式。在Koa2框架中,理解这些方法的差异和适用场景对构建高效Web应用至关重要。

GET方法

GET是最常用的HTTP方法,用于请求指定资源。它只用于数据获取,不应产生副作用。GET请求的参数会附加在URL后面,通过查询字符串传递。

router.get('/api/users', async (ctx) => {
  const users = await User.find();
  ctx.body = users;
});

GET请求的特点:

  • 可被缓存
  • 保留在浏览器历史记录中
  • 可被收藏为书签
  • 有长度限制(约2048字符)
  • 不应处理敏感数据

POST方法

POST用于向指定资源提交数据,通常会导致服务器状态变化。POST请求将数据放在请求体中发送,适合传输大量或敏感数据。

router.post('/api/users', async (ctx) => {
  const newUser = new User(ctx.request.body);
  await newUser.save();
  ctx.status = 201;
  ctx.body = newUser;
});

POST请求的典型场景:

  • 提交表单数据
  • 上传文件
  • 创建新资源
  • 触发业务流程

PUT方法

PUT用于完整更新资源,客户端需要提供更新后的完整资源。如果资源不存在,PUT可能会创建新资源。

router.put('/api/users/:id', async (ctx) => {
  const updatedUser = await User.findByIdAndUpdate(
    ctx.params.id,
    ctx.request.body,
    { new: true }
  );
  ctx.body = updatedUser;
});

PUT与POST的关键区别:

  • PUT是幂等的(多次调用效果相同)
  • PUT需要指定完整资源标识符
  • PUT通常用于替换现有资源

PATCH方法

PATCH用于对资源进行部分更新,只需发送需要修改的字段。与PUT不同,PATCH不需要发送完整资源。

router.patch('/api/users/:id', async (ctx) => {
  const updatedUser = await User.findByIdAndUpdate(
    ctx.params.id,
    { $set: ctx.request.body },
    { new: true }
  );
  ctx.body = updatedUser;
});

PATCH的常见使用模式:

  • JSON Patch格式
  • JSON Merge Patch格式
  • 自定义部分更新逻辑

DELETE方法

DELETE用于删除指定的资源。成功执行后,服务器应返回204状态码(无内容)或200状态码(包含删除确认信息)。

router.delete('/api/users/:id', async (ctx) => {
  await User.findByIdAndDelete(ctx.params.id);
  ctx.status = 204;
});

DELETE请求的注意事项:

  • 可以是逻辑删除(标记为删除状态)
  • 也可以是物理删除(从数据库移除)
  • 应考虑级联删除相关资源

HEAD方法

HEAD与GET类似,但服务器只返回头部信息,不返回实际内容。常用于检查资源是否存在或验证缓存有效性。

router.head('/api/users/:id', async (ctx) => {
  const userExists = await User.exists({ _id: ctx.params.id });
  if (!userExists) ctx.throw(404);
  ctx.status = 200;
});

HEAD的典型用途:

  • 获取资源的元数据
  • 检查链接有效性
  • 验证缓存是否过期

OPTIONS方法

OPTIONS用于获取目标资源支持的通信选项。在CORS预检请求中,浏览器会自动发送OPTIONS请求。

router.options('/api/users', async (ctx) => {
  ctx.set('Allow', 'GET, POST, OPTIONS');
  ctx.status = 204;
});

OPTIONS请求的应用场景:

  • CORS预检请求
  • 发现服务器支持的方法
  • API自描述

TRACE方法

TRACE用于诊断目的,服务器会将收到的请求消息原样返回。Koa2默认不实现TRACE方法,因安全考虑通常应禁用。

// 不建议在生产环境启用TRACE
app.use(async (ctx, next) => {
  if (ctx.method === 'TRACE') {
    ctx.body = ctx.request;
  } else {
    await next();
  }
});

TRACE的安全风险:

  • 可能暴露敏感信息
  • 可用于跨站跟踪攻击
  • 通常应返回405 Method Not Allowed

CONNECT方法

CONNECT用于建立到目标资源的隧道,通常用于SSL隧道。在Koa2应用中很少需要直接处理CONNECT请求。

// 通常由底层服务器处理CONNECT
app.use(async (ctx, next) => {
  if (ctx.method === 'CONNECT') {
    ctx.throw(501, 'CONNECT not implemented');
  }
  await next();
});

CONNECT的主要用途:

  • HTTP代理
  • SSL隧道
  • 建立网络连接

方法特性比较

方法 安全 幂等 缓存 请求体 响应体
GET
POST
PUT
PATCH
DELETE 可有
HEAD
OPTIONS
TRACE
CONNECT

Koa2中的方法处理

Koa2的koa-router支持所有标准HTTP方法,并提供简洁的API定义路由:

const Router = require('koa-router');
const router = new Router();

router.get('/resource', handleGet);
router.post('/resource', handlePost);
router.put('/resource/:id', handlePut);
router.del('/resource/:id', handleDelete); // 注意是del不是delete
router.all('/resource', handleAllMethods);

对于不常用的方法,可以使用更灵活的API:

router.register('/resource', ['LOCK', 'UNLOCK'], handleLockMethods);

自定义方法处理

虽然HTTP标准定义了有限的方法,但实际应用中可能需要扩展。Koa2允许处理自定义方法:

app.use(async (ctx, next) => {
  if (ctx.method === 'PURGE') {
    // 处理缓存清除逻辑
    await clearCache();
    ctx.status = 204;
  } else {
    await next();
  }
});

常见自定义方法示例:

  • PURGE - 清除缓存
  • LINK/UNLINK - 资源关联
  • VIEW - 只读操作
  • COPY/MOVE - 资源操作

方法的安全性与幂等性

理解方法的安全性和幂等性对API设计至关重要:

安全性:不会修改资源的请求方法(GET、HEAD、OPTIONS)

// 安全方法不应修改状态
router.get('/api/config', async (ctx) => {
  // 错误示范:在GET中修改数据
  // await updateLastAccessTime(); 
  ctx.body = await getConfig();
});

幂等性:多次执行效果相同的操作(GET、PUT、DELETE、HEAD、OPTIONS)

// 幂等方法示例
router.put('/api/counter', async (ctx) => {
  // 无论执行多少次,结果都是counter=5
  await setCounter(5);
  ctx.body = { value: 5 };
});

方法的选择原则

设计RESTful API时应根据操作语义选择合适的方法:

  1. 获取数据 - GET
  2. 创建资源 - POST(客户端不知道URI)或PUT(客户端指定URI)
  3. 完整更新 - PUT
  4. 部分更新 - PATCH
  5. 删除 - DELETE
  6. 元数据查询 - HEAD
  7. 跨域预检 - OPTIONS

错误示范:

// 错误:用GET执行删除操作
router.get('/api/users/delete/:id', async (ctx) => {
  await User.deleteOne({ _id: ctx.params.id });
  ctx.redirect('/users');
});

正确做法:

// 正确:使用DELETE方法
router.delete('/api/users/:id', async (ctx) => {
  await User.deleteOne({ _id: ctx.params.id });
  ctx.status = 204;
});

方法的状态码对应

不同方法成功执行时应返回适当的状态码:

  • GET:200 OK(成功)或304 Not Modified(缓存有效)
  • POST:201 Created(资源创建)或202 Accepted(异步处理)
  • PUT:200 OK或204 No Content
  • PATCH:200 OK或204 No Content
  • DELETE:204 No Content
  • HEAD:200 OK(不返回内容)
  • OPTIONS:204 No Content或200 OK(包含Allow头)
router.post('/api/async-jobs', async (ctx) => {
  const job = createAsyncJob(ctx.request.body);
  ctx.status = 202; // 异步处理中
  ctx.body = {
    jobId: job.id,
    statusUrl: `/api/jobs/${job.id}`
  };
});

方法的缓存行为

不同HTTP方法的缓存特性差异显著:

GET请求可被缓存:

router.get('/api/products', async (ctx) => {
  const products = await getProducts();
  ctx.set('Cache-Control', 'public, max-age=3600');
  ctx.body = products;
});

POST请求通常不应缓存:

router.post('/api/orders', async (ctx) => {
  const order = await createOrder(ctx.request.body);
  ctx.set('Cache-Control', 'no-store');
  ctx.status = 201;
  ctx.body = order;
});

方法的Content-Type处理

不同方法对请求体的处理方式不同:

POST/PUT/PATCH通常需要内容类型:

router.post('/api/upload', async (ctx) => {
  if (!ctx.is('multipart/form-data')) {
    ctx.throw(415, 'Unsupported Media Type');
  }
  const file = ctx.request.files.file;
  // 处理上传...
});

GET请求通常不带请求体:

router.get('/api/search', async (ctx) => {
  // 参数应通过查询字符串传递
  const { q } = ctx.query;
  const results = await search(q);
  ctx.body = results;
});

方法的性能考量

不同HTTP方法对性能的影响:

GET请求最轻量:

// 简单GET处理
router.get('/api/status', (ctx) => {
  ctx.body = { status: 'ok', timestamp: Date.now() };
});

POST/PUT请求需要更多处理:

router.post('/api/data', async (ctx) => {
  // 验证数据
  const { error } = validateData(ctx.request.body);
  if (error) ctx.throw(400, error.message);
  
  // 处理数据
  const result = await processData(ctx.request.body);
  
  // 响应
  ctx.status = 201;
  ctx.body = result;
});

方法的版本兼容

API演进时方法语义应保持稳定:

不推荐修改现有方法语义:

// v1: GET返回用户列表
router.get('/api/users', getUsers);

// v2错误示范:修改GET语义为返回活跃用户
router.get('/api/users', getActiveUsers); 

// v2正确做法:添加新端点或使用查询参数
router.get('/api/users', (ctx) => {
  const activeOnly = ctx.query.active === 'true';
  return activeOnly ? getActiveUsers() : getUsers();
});

方法的测试策略

针对不同方法需要不同的测试重点:

GET方法测试示例:

describe('GET /api/products', () => {
  it('should return all products', async () => {
    const res = await request(app)
      .get('/api/products')
      .expect(200);
    expect(res.body).to.be.an('array');
  });
});

POST方法测试示例:

describe('POST /api/products', () => {
  it('should create a new product', async () => {
    const newProduct = { name: 'Test', price: 100 };
    const res = await request(app)
      .post('/api/products')
      .send(newProduct)
      .expect(201);
    expect(res.body).to.have.property('id');
  });
});

方法的错误处理

不同方法需要特定的错误响应:

GET资源不存在:

router.get('/api/users/:id', async (ctx) => {
  const user = await User.findById(ctx.params.id);
  if (!user) ctx.throw(404, 'User not found');
  ctx.body = user;
});

POST数据验证失败:

router.post('/api/users', async (ctx) => {
  const { error } = userSchema.validate(ctx.request.body);
  if (error) ctx.throw(400, error.details[0].message);
  
  const user = new User(ctx.request.body);
  await user.save();
  ctx.status = 201;
  ctx.body = user;
});

方法的幂等性实现

确保PUT/DELETE等方法的幂等性:

PUT幂等实现:

router.put('/api/settings', async (ctx) => {
  // 无论调用多少次,结果相同
  await Settings.updateOne(
    {},
    { $set: ctx.request.body },
    { upsert: true }
  );
  ctx.body = await Settings.findOne();
});

DELETE幂等处理:

router.delete('/api/temp-data/:id', async (ctx) => {
  // 无论资源是否存在都返回204
  await TempData.deleteOne({ _id: ctx.params.id });
  ctx.status = 204;
});

方法的Content Negotiation

根据Accept头支持不同响应格式:

GET支持JSON和XML:

router.get('/api/books', async (ctx) => {
  const books = await Book.find();
  
  if (ctx.accepts('json')) {
    ctx.type = 'json';
    ctx.body = books;
  } else if (ctx.accepts('xml')) {
    ctx.type = 'xml';
    ctx.body = generateXML(books);
  } else {
    ctx.throw(406, 'Not Acceptable');
  }
});

方法的速率限制

对不同方法实施不同限流策略:

POST严格限流:

const postLimiter = rateLimit({
  windowMs: 15 * 60 * 1000, // 15分钟
  max: 100 // 每IP最多100次POST请求
});

router.post('/api/comments', postLimiter, async (ctx) => {
  // 处理评论提交
});

GET宽松限流:

const getLimiter = rateLimit({
  windowMs: 15 * 60 * 1000,
  max: 1000 // 每IP最多1000次GET请求
});

router.get('/api/public-data', getLimiter, async (ctx) => {
  // 返回公开数据
});

方法的安全防护

针对不同方法实施安全措施:

防止CSRF(通常保护POST/PUT/PATCH/DELETE):

app.use(koaCsrf());
router.post('/api/transfer', async (ctx) => {
  // 自动验证CSRF令牌
  await processTransfer(ctx.request.body);
  ctx.status = 200;
});

GET请求的XSS防护:

router.get('/api/search', async (ctx) => {
  const query = xssFilter(ctx.query.q);
  const results = await search(query);
  ctx.body = results;
});

方法的实时通信

结合WebSocket时的方法使用:

HTTP初始化,WebSocket持续通信:

router.post('/api/chat', async (ctx) => {
  const chat = await createChat(ctx.request.body);
  ctx.status = 201;
  ctx.body = { 
    chatId: chat.id,
    wsUrl: `/ws/chat/${chat.id}`
  };
});

方法的监控统计

记录不同方法的访问指标:

app.use(async (ctx, next) => {
  const start = Date.now();
  await next();
  const duration = Date.now() - start;
  
  statsd.increment(`http.${ctx.method}.${ctx.status}`);
  statsd.timing(`http.${ctx.method}`, duration);
});

router.get('/api/metrics', async (ctx) => {
  ctx.body = {
    counts: await getMethodCounts(),
    timings: await getMethodTimings()
  };
});

方法的文档生成

基于方法定义自动生成API文档:

/**
 * @api {get} /api/users/:id 获取用户信息
 * @apiName GetUser
 * @apiGroup User
 */
router.get('/api/users/:id', getUser);

/**
 * @api {post} /api/users 创建用户
 * @apiName CreateUser
 * @apiGroup User
 */
router.post('/api/users', createUser);

方法的代理转发

在网关层处理不同方法的转发:

router.all('/api/*', async (ctx) => {
  const target = determineTarget(ctx.method, ctx.path);
  const response = await proxyRequest(ctx.method, target, ctx);
  ctx.status = response.status;
  ctx.body = response.data;
});

方法的灰度发布

根据方法实现不同的发布策略:

router.get('/api/new-feature', async (ctx) => {
  if (shouldEnableNewFeature(ctx)) {
    ctx.body = await newFeatureImplementation();
  } else {
    ctx.body = await legacyImplementation();
  }
});

方法的A/B测试

针对不同方法实施差异化测试:

router.post('/api/checkout', async (ctx) => {
  const variant = getABTestVariant(ctx);
  if (variant === 'B') {
    await newCheckoutFlow(ctx.request.body);
  } else {
    await legacyCheckoutFlow(ctx.request.body);
  }
  ctx.status = 200;
});

方法的流量复制

将生产流量复制到测试环境:

const shadowRouter = new Router();
shadowRouter.post('/api/orders', async (ctx) => {
  // 异步复制到测试环境
  shadowRequest('POST',

本站部分内容来自互联网,一切版权均归源网站或源作者所有。

如果侵犯了你的权益请来信告知我们删除。邮箱:cc@cccx.cn

前端川

前端川,陈川的代码茶馆🍵,专治各种不服的Bug退散符💻,日常贩卖秃头警告级的开发心得🛠️,附赠一行代码笑十年的摸鱼宝典🐟,偶尔掉落咖啡杯里泡开的像素级浪漫☕。‌