Node.js GET/POST请求

查询参数、表单体、JSON 体、文件上传,以及两者的边界。

Intermediate
Key PointsGET 的参数在 URL 里,POST 的参数在请求体里——这是唯一的本质区别。

GET 取参数

JavaScript
const http = require('node:http');

const server = http.createServer((req, res) => {
  const url = new URL(req.url, `http://${req.headers.host}`);

  // /search?q=node&page=2
  const q = url.searchParams.get('q') || '';
  const page = Number(url.searchParams.get('page') || 1);

  // 同名多值:?tag=a&tag=b
  const tags = url.searchParams.getAll('tag');

  res.setHeader('Content-Type', 'application/json; charset=utf-8');
  res.end(JSON.stringify({ q, page, tags }));
});

server.listen(3000);
Output
$ curl "http://127.0.0.1:3000/search?q=node&page=2&tag=a&tag=b"
{"q":"node","page":2,"tags":["a","b"]}

POST 读请求体

JavaScript
function readBody(req, limit = 1024 * 1024) {
  return new Promise((resolve, reject) => {
    let size = 0;
    const chunks = [];

    req.on('data', (c) => {
      size += c.length;
      if (size > limit) {
        reject(new Error('请求体过大'));
        req.destroy();
        return;
      }
      chunks.push(c);
    });
    req.on('end', () => resolve(Buffer.concat(chunks).toString('utf8')));
    req.on('error', reject);
  });
}

const server = http.createServer(async (req, res) => {
  if (req.method === 'POST' && req.url === '/login') {
    const raw = await readBody(req);
    const ct = req.headers['content-type'] || '';

    let data;
    if (ct.includes('application/json')) {
      data = JSON.parse(raw);
    } else {
      data = Object.fromEntries(new URLSearchParams(raw));
    }

    res.setHeader('Content-Type', 'application/json; charset=utf-8');
    return res.end(JSON.stringify({ got: data }));
  }
  res.writeHead(404).end();
});
Output
$ curl -X POST http://127.0.0.1:3000/login \
    -H "Content-Type: application/json" \
    -d '{"user":"tom"}'
{"got":{"user":"tom"}}

$ curl -X POST http://127.0.0.1:3000/login -d "user=tom&pwd=1"
{"got":{"user":"tom","pwd":"1"}}
Always cap the body size. Without a limit a crafted 10 GB POST exhausts memory. Also set it at the reverse proxy (nginx client_max_body_size).

GET 还是 POST

AspectGETPOST
参数位置URL 查询串请求体
可缓存是默认否
可收藏/分享是否
幂等是否
长度限制浏览器约 2KB服务端配置决定
浏览器历史留在历史里不保留

Read with GET, mutate with POST/PUT/PATCH/DELETE. Making "delete" a GET link lets browser prefetch and crawlers trigger it — a real source of incidents.

文件上传

JavaScript
// 原生 multipart 解析很麻烦,用 multer
const multer = require('multer');
const upload = multer({ dest: 'uploads/', limits: { fileSize: 5 * 1024 * 1024 } });

app.post('/upload', upload.single('file'), (req, res) => {
  res.json({ name: req.file.originalname, size: req.file.size });
});

Friends & Partners