阿里云主机折上折
  • 微信号
Current Site:Index > Comprehensive Analysis of HTTP Request Methods

Comprehensive Analysis of HTTP Request Methods

Author:Chuan Chen 阅读数:63865人阅读 分类: Node.js

Overview of HTTP Request Methods

The HTTP protocol defines multiple request methods to represent different types of operations on resources. These methods form the foundation of RESTful architecture and determine how clients interact with servers. In the Koa2 framework, understanding the differences and applicable scenarios of these methods is crucial for building efficient web applications.

GET Method

GET is the most commonly used HTTP method for requesting specified resources. It is only used for data retrieval and should not produce side effects. GET request parameters are appended to the URL and passed via query strings.

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

Characteristics of GET requests:

  • Can be cached
  • Remain in browser history
  • Can be bookmarked
  • Have length limitations (~2048 characters)
  • Should not handle sensitive data

POST Method

POST is used to submit data to specified resources, usually causing server state changes. POST requests send data in the request body, making them suitable for transmitting large or sensitive data.

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

Typical scenarios for POST requests:

  • Submitting form data
  • Uploading files
  • Creating new resources
  • Triggering business processes

PUT Method

PUT is used for complete resource updates, requiring the client to provide the complete updated resource. If the resource doesn't exist, PUT may create a new resource.

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

Key differences between PUT and POST:

  • PUT is idempotent (multiple calls have the same effect)
  • PUT requires specifying the complete resource identifier
  • PUT is typically used to replace existing resources

PATCH Method

PATCH is used for partial resource updates, only requiring the fields that need modification. Unlike PUT, PATCH doesn't need to send the complete resource.

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

Common PATCH usage patterns:

  • JSON Patch format
  • JSON Merge Patch format
  • Custom partial update logic

DELETE Method

DELETE is used to remove specified resources. After successful execution, the server should return status code 204 (No Content) or 200 (with deletion confirmation).

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

DELETE request considerations:

  • Can be logical deletion (marked as deleted)
  • Can be physical deletion (removed from database)
  • Should consider cascading deletion of related resources

HEAD Method

HEAD is similar to GET, but the server only returns headers without the actual content. Often used to check resource existence or validate cache validity.

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

Typical uses of HEAD:

  • Getting resource metadata
  • Checking link validity
  • Verifying cache expiration

OPTIONS Method

OPTIONS is used to get the communication options supported by the target resource. In CORS preflight requests, browsers automatically send OPTIONS requests.

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

OPTIONS request applications:

  • CORS preflight requests
  • Discovering server-supported methods
  • API self-description

TRACE Method

TRACE is used for diagnostic purposes, with the server returning the received request message exactly as received. Koa2 doesn't implement TRACE by default, and it should generally be disabled for security reasons.

// Not recommended to enable TRACE in production
app.use(async (ctx, next) => {
  if (ctx.method === 'TRACE') {
    ctx.body = ctx.request;
  } else {
    await next();
  }
});

TRACE security risks:

  • May expose sensitive information
  • Can be used for cross-site tracing attacks
  • Should typically return 405 Method Not Allowed

CONNECT Method

CONNECT is used to establish a tunnel to the target resource, typically for SSL tunnels. Koa2 applications rarely need to handle CONNECT requests directly.

// Usually handled by the underlying server
app.use(async (ctx, next) => {
  if (ctx.method === 'CONNECT') {
    ctx.throw(501, 'CONNECT not implemented');
  }
  await next();
});

Main uses of CONNECT:

  • HTTP proxies
  • SSL tunnels
  • Establishing network connections

Method Characteristics Comparison

Method Safe Idempotent Cacheable Request Body Response Body
GET Yes Yes Yes No Yes
POST No No No Yes Yes
PUT No Yes No Yes Yes
PATCH No No No Yes Yes
DELETE No Yes No No Optional
HEAD Yes Yes Yes No No
OPTIONS Yes Yes No No Yes
TRACE Yes Yes No No Yes
CONNECT No No No Yes Yes

Method Handling in Koa2

Koa2's koa-router supports all standard HTTP methods and provides a concise API for defining routes:

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); // Note: del not delete
router.all('/resource', handleAllMethods);

For less common methods, more flexible APIs can be used:

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

Custom Method Handling

Although HTTP standards define limited methods, real-world applications may need extensions. Koa2 allows handling custom methods:

app.use(async (ctx, next) => {
  if (ctx.method === 'PURGE') {
    // Handle cache clearing logic
    await clearCache();
    ctx.status = 204;
  } else {
    await next();
  }
});

Common custom method examples:

  • PURGE - Clear cache
  • LINK/UNLINK - Resource association
  • VIEW - Read-only operations
  • COPY/MOVE - Resource operations

Method Safety and Idempotence

Understanding method safety and idempotence is crucial for API design:

Safety: Request methods that don't modify resources (GET, HEAD, OPTIONS)

// Safe methods shouldn't modify state
router.get('/api/config', async (ctx) => {
  // Bad practice: modifying data in GET
  // await updateLastAccessTime(); 
  ctx.body = await getConfig();
});

Idempotence: Operations with the same effect when executed multiple times (GET, PUT, DELETE, HEAD, OPTIONS)

// Idempotent method example
router.put('/api/counter', async (ctx) => {
  // No matter how many times executed, result is always counter=5
  await setCounter(5);
  ctx.body = { value: 5 };
});

Method Selection Principles

When designing RESTful APIs, choose appropriate methods based on operation semantics:

  1. Retrieve data - GET
  2. Create resources - POST (client doesn't know URI) or PUT (client specifies URI)
  3. Complete update - PUT
  4. Partial update - PATCH
  5. Delete - DELETE
  6. Metadata query - HEAD
  7. Cross-origin preflight - OPTIONS

Bad example:

// Wrong: Using GET for delete operation
router.get('/api/users/delete/:id', async (ctx) => {
  await User.deleteOne({ _id: ctx.params.id });
  ctx.redirect('/users');
});

Correct approach:

// Correct: Using DELETE method
router.delete('/api/users/:id', async (ctx) => {
  await User.deleteOne({ _id: ctx.params.id });
  ctx.status = 204;
});

Method Status Code Mapping

Different methods should return appropriate status codes upon successful execution:

  • GET: 200 OK (success) or 304 Not Modified (cache valid)
  • POST: 201 Created (resource created) or 202 Accepted (async processing)
  • PUT: 200 OK or 204 No Content
  • PATCH: 200 OK or 204 No Content
  • DELETE: 204 No Content
  • HEAD: 200 OK (no content returned)
  • OPTIONS: 204 No Content or 200 OK (with Allow header)
router.post('/api/async-jobs', async (ctx) => {
  const job = createAsyncJob(ctx.request.body);
  ctx.status = 202; // Async processing
  ctx.body = {
    jobId: job.id,
    statusUrl: `/api/jobs/${job.id}`
  };
});

Method Caching Behavior

Different HTTP methods have significant caching characteristic differences:

GET requests can be cached:

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

POST requests typically shouldn't be cached:

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 Handling by Method

Different methods handle request bodies differently:

POST/PUT/PATCH typically need content types:

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

GET requests typically don't have request bodies:

router.get('/api/search', async (ctx) => {
  // Parameters should be passed via query string
  const { q } = ctx.query;
  const results = await search(q);
  ctx.body = results;
});

Performance Considerations by Method

Different HTTP methods impact performance differently:

GET requests are lightest:

// Simple GET handling
router.get('/api/status', (ctx) => {
  ctx.body = { status: 'ok', timestamp: Date.now() };
});

POST/PUT requests require more processing:

router.post('/api/data', async (ctx) => {
  // Validate data
  const { error } = validateData(ctx.request.body);
  if (error) ctx.throw(400, error.message);
  
  // Process data
  const result = await processData(ctx.request.body);
  
  // Respond
  ctx.status = 201;
  ctx.body = result;
});

Version Compatibility for Methods

Method semantics should remain stable during API evolution:

Not recommended to modify existing method semantics:

// v1: GET returns user list
router.get('/api/users', getUsers);

// v2 bad example: Changing GET semantics to return active users
router.get('/api/users', getActiveUsers); 

// v2 correct approach: Add new endpoint or use query parameters
router.get('/api/users', (ctx) => {
  const activeOnly = ctx.query.active === 'true';
  return activeOnly ? getActiveUsers() : getUsers();
});

Testing Strategies by Method

Different methods require different testing focuses:

GET method test example:

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 method test example:

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');
  });
});

Error Handling by Method

Different methods require specific error responses:

GET resource not found:

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 data validation failure:

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;
});

Implementing Method Idempotence

Ensuring idempotence for PUT/DELETE methods:

PUT idempotent implementation:

router.put('/api/settings', async (ctx) => {
  // Same result no matter how many times called
  await Settings.updateOne(
    {},
    { $set: ctx.request.body },
    { upsert: true }
  );
  ctx.body = await Settings.findOne();
});

DELETE idempotent handling:

router.delete('/api/temp-data/:id', async (ctx) => {
  // Returns 204 whether resource exists or not
  await TempData.deleteOne({ _id: ctx.params.id });
  ctx.status = 204;
});

Content Negotiation by Method

Supporting different response formats based on Accept header:

GET supporting JSON and 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');
  }
});

Rate Limiting by Method

Implementing different rate limiting strategies:

Strict rate limiting for POST:

const postLimiter = rateLimit({
  windowMs: 15 * 60 * 1000, // 15 minutes
  max: 100 // Max 100 POST requests per IP
});

router.post('/api/comments', postLimiter, async (ctx) => {
  // Handle comment submission
});

Loose rate limiting for GET:

const getLimiter = rateLimit({
  windowMs: 15 * 60 * 1000,
  max: 1000 // Max 1000 GET requests per IP
});

router.get('/api/public-data', getLimiter, async (ctx) => {
  // Return public data
});

Security Protection by Method

Implementing security measures for different methods:

CSRF protection (typically for POST/PUT/PATCH/DELETE):

app.use(koaCsrf());
router.post('/api/transfer', async (ctx) => {
  // Automatically validates CSRF token
  await processTransfer(ctx.request.body);
  ctx.status = 200;
});

XSS protection for GET requests:

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

Real-time Communication with Methods

Method usage combined with WebSocket:

HTTP initialization, WebSocket for continuous communication:

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}`
  };
});

Monitoring and Statistics by Method

Recording access metrics for different methods:

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()
  };
});

Documentation Generation from Methods

Automatically generating API documentation based on method definitions:

/**
 * @api {get} /api/users/:id Get user info
 * @apiName GetUser
 * @apiGroup User
 */
router.get('/api/users/:id', getUser);

/**
 * @api {post} /api/users Create user
 * @apiName CreateUser
 * @apiGroup User
 */
router.post('/api/users', createUser);

Proxy Forwarding by Method

Handling method forwarding at the gateway layer:

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;
});

Canary Releases by Method

Implementing different release strategies based on methods:

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

A/B Testing by Method

Implementing differentiated tests for different methods:

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;
});

Traffic Mirroring by Method

Copying production traffic to test environments:

const shadowRouter = new Router();
shadowRouter.post

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

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

Front End Chuan

Front End Chuan, Chen Chuan's Code Teahouse 🍵, specializing in exorcising all kinds of stubborn bugs 💻. Daily serving baldness-warning-level development insights 🛠️, with a bonus of one-liners that'll make you laugh for ten years 🐟. Occasionally drops pixel-perfect romance brewed in a coffee cup ☕.