Comprehensive Analysis of HTTP Request Methods
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:
- Retrieve data - GET
- Create resources - POST (client doesn't know URI) or PUT (client specifies URI)
- Complete update - PUT
- Partial update - PATCH
- Delete - DELETE
- Metadata query - HEAD
- 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
上一篇:大型项目路由拆分方案