Menu
ESC

Nhập từ khóa để tìm kiếm

↑↓ Di chuyển
Enter Mở
ESC Đóng

Đang tải...

k6 — HTTP requests & cookies

Performance Testing with JMeter and k6 Bài 17/60

Mở đầu — vì sao bài này quan trọng

Bạn đã cài xong k6, hiểu về options, stages, scenarios, checksthresholds. Nhưng tất cả những thứ đó chỉ là bộ khung điều phối. Trái tim thực sự của một bài test hiệu năng nằm ở chỗ: mỗi virtual user gửi request như thế nào? Nếu request bạn gửi đi không giống với cách người dùng thật (hoặc client thật) gọi API, thì mọi con số throughput, latency, error rate bạn thu về sau đó đều là số ảo — đẹp mà vô nghĩa.

Đây chính là lý do bài học này quan trọng bậc nhất trong nhóm bài về k6. Một request POST đăng nhập thiếu header Content-Type đúng, một cookie session không được giữ qua các bước, một body JSON sai định dạng — bất kỳ lỗi nhỏ nào cũng khiến server trả về 400/401 và bạn "test" cả nghìn user trên một đường dẫn lỗi mà không hề hay biết. Tôi đã gặp không ít bạn chạy load test 30 phút, hớn hở báo cáo "hệ thống chịu được 5.000 RPS" — hóa ra 5.000 RPS đó toàn là response 401 Unauthorized vì quên gửi token.

Trong bài này, chúng ta sẽ đi thật kỹ vào module k6/http: cách gửi GET/POST/PUT/DELETE, cách xử lý JSON body, headers, params, và đặc biệt là cách k6 quản lý cookie — thứ mà nhiều người dùng JMeter chuyển sang k6 hay bị vấp. Nắm chắc phần này, bạn mới có thể mô phỏng đúng một luồng người dùng thật ở các bài sau.

Khái niệm cốt lõi

Module k6/http và cách một request được biểu diễn

Mọi request trong k6 đều đi qua module tích hợp sẵn k6/http. Bạn import nó ở đầu file:

import http from 'k6/http';

Mỗi hàm như http.get(), http.post() sẽ thực sự gửi một request HTTP ngay tại thời điểm gọi và trả về một object Response. Điều quan trọng cần khắc sâu: đây là request thật, không phải mô phỏng ở tầng trình duyệt. k6 không chạy JavaScript của trang web, không tải CSS/ảnh tự động, không render DOM. Nó là một HTTP client thuần — giống như bạn dùng curl nhưng lập trình được và nhân bản ra hàng nghìn VU song song.

Các phương thức cơ bản

k6 cung cấp hàm riêng cho từng HTTP method phổ biến:

http.get(url, [params]);
http.post(url, [body], [params]);
http.put(url, [body], [params]);
http.patch(url, [body], [params]);
http.del(url, [body], [params]);   // lưu ý: del chứ không phải delete (từ khoá reserved)
http.head(url, [params]);

Ngoài ra có http.request(method, url, body, params) dạng tổng quát khi bạn cần method động.

Ví dụ đơn giản nhất — gọi API danh sách sản phẩm:

import http from 'k6/http';

export default function () { const res = http.get('https://api.shop.vn/products'); console.log(res.status); // 200 console.log(res.body.length); // số byte body }

Gửi POST với JSON body

Đây là chỗ hay sai nhất. Server API hiện đại thường nhận JSON, nên bạn phải tự serialize object thành chuỗi JSON bằng JSON.stringify() và khai báo header Content-Type: application/json. k6 KHÔNG tự đoán giúp bạn.

import http from 'k6/http';

export default function () { const payload = JSON.stringify({ username: 'nguyenvana', password: 'MatKhau@123', });

const params = { headers: { 'Content-Type': 'application/json' }, };

const res = http.post('https://api.shop.vn/auth/login', payload, params); }

Nếu bạn truyền thẳng object { username: ... }không JSON.stringifykhông set Content-Type, k6 sẽ mặc định gửi dưới dạng application/x-www-form-urlencoded (giống submit form HTML). Với một số API đây là hành vi mong muốn — nhưng với API JSON thì server sẽ trả 400 hoặc 415 Unsupported Media Type.

Object params — nơi cấu hình mọi thứ

Tham số params (tuỳ chọn) là nơi bạn điều khiển headers, query string, cookies, tags, timeout, redirect... Một vài trường hay dùng:

const params = {
  headers: {
    'Content-Type': 'application/json',
    'Authorization': Bearer ${token},
  },
  tags: { name: 'ProductList' },   // gộp nhiều URL động vào 1 metric
  timeout: '30s',                  // mặc định 60s
};

Mẹo về tags.name: khi URL có tham số động (ví dụ /products/123, /products/456), k6 mặc định coi mỗi URL là một entry riêng trong báo cáo, làm loãng số liệu. Gán chung tags: { name: 'ProductDetail' } sẽ gom chúng lại thành một dòng thống kê duy nhất.

Đọc Response

Object trả về cho bạn mọi thứ cần để verify:

const res = http.get('https://api.shop.vn/products');

res.status; // 200 res.body; // chuỗi body thô res.json(); // parse JSON -> object (dùng res.json('data.items') để truy sâu) res.headers['Content-Type']; res.timings.duration; // tổng thời gian request (ms) res.timings.waiting; // TTFB - time to first byte res.cookies; // cookies server trả về trong response này

res.json('key.path') là cú pháp rất tiện: res.json('data.total') sẽ tự đi vào data.total mà không cần parse thủ công.

Cookies trong k6 — cơ chế cần hiểu rõ

Đây là điểm mấu chốt của bài. k6 mặc định có một cookie jar cho mỗi VU và cookie jar này được reset lại ở đầu mỗi iteration (mỗi lần hàm default chạy lại). Ý nghĩa:

  • Trong cùng một iteration, nếu server trả Set-Cookie ở request đăng nhập, thì request tiếp theo trong cùng iteration đó sẽ tự động gửi kèm cookie — bạn không phải làm gì cả. Đây là hành vi giống trình duyệt.
  • Sang iteration mới, cookie jar sạch trơn. Điều này mô phỏng đúng một phiên người dùng độc lập.
Bạn có thể can thiệp cookie jar khi cần:

const jar = http.cookieJar();                    // jar của VU hiện tại
jar.set('https://api.shop.vn', 'session', 'abc123');
const cookies = jar.cookiesForURL('https://api.shop.vn');

Hoặc set cookie riêng cho một request qua params.cookies:

http.get('https://api.shop.vn/cart', {
  cookies: { session: 'abc123' },
});

Phân biệt rõ: session-based auth dựa vào cookie (server tự set, k6 tự giữ trong iteration). Token-based auth (JWT) thì bạn phải tự bắt token từ response login rồi gắn vào header Authorization cho các request sau — cookie jar không giúp gì ở đây.

Tình huống thực tế

Tình huống 1: Sàn Tiki test API giỏ hàng nhưng quên giữ session

Một nhóm QA giả định làm việc cho sàn thương mại điện tử lớn tại Việt Nam cần load test luồng: đăng nhập → xem sản phẩm → thêm vào giỏ. Bạn dev viết script tách hai file test riêng biệt, và trong file "thêm giỏ hàng", họ gọi thẳng http.post('/cart/add', ...) mà không đăng nhập trước trong cùng iteration.

Kết quả: 100% request trả về 401. Nhưng vì họ chỉ nhìn vào biểu đồ latency (thấy p95 chỉ 40ms, rất "đẹp"), họ tưởng hệ thống mạnh. Thực chất server chỉ mất 40ms để... từ chối. Khi thêm một check(res, { 'status is 201': (r) => r.status === 201 }) họ mới phát hiện tỷ lệ đúng là 0%.

Bài học: cookie jar reset mỗi iteration, nên login và các bước phụ thuộc session phải nằm chung trong một iteration. Và luôn check status thực tế, đừng tin mỗi latency.

Tình huống 2: Ứng dụng ví MoMo-style dùng JWT, cookie jar không cứu được

Một fintech giả định phát triển ví điện tử dùng JWT thay vì session cookie. Kỹ sư mới chuyển từ JMeter sang k6, quen với "JMeter tự quản cookie", nên cứ nghĩ k6 cũng tự gắn token. Anh viết:

export default function () {
  http.post('https://api.viwallet.vn/login', loginPayload, jsonParams);
  http.get('https://api.viwallet.vn/balance');   // 401!
}

Request /balance liên tục 401 vì JWT không nằm trong cookie — nó nằm trong body response login, và server đòi header Authorization: Bearer. Fix đúng:

export default function () {
  const loginRes = http.post('https://api.viwallet.vn/login', loginPayload, jsonParams);
  const token = loginRes.json('access_token');

const authParams = { headers: { 'Authorization': Bearer ${token} }, }; const res = http.get('https://api.viwallet.vn/balance', authParams); check(res, { 'balance loaded': (r) => r.status === 200 }); }

Bài học: phân biệt cookie-based và token-based auth. Cookie jar chỉ tự động với Set-Cookie; token phải tự bắt và tự gắn header.

Tình huống 3: Grab Food test API đặt món, gửi form thay vì JSON

Một team giả định làm ứng dụng giao đồ ăn cần test API POST /orders. Backend nhận JSON. Nhưng dev viết:

http.post('https://api.grabfood.vn/orders', {
  restaurantId: 42,
  items: [{ id: 7, qty: 2 }],
});

Vì truyền object trực tiếp, k6 tự động encode thành x-www-form-urlencoded, và mảng items bị mã hoá thành chuỗi khó hiểu. Server trả 400 "invalid body". Sau nhiều giờ debug, họ dùng res.request.body để in ra body thực k6 đã gửi và phát hiện nó không phải JSON. Fix bằng JSON.stringify + Content-Type: application/json.

Bài học: với body phức tạp (nested object, mảng), luôn dùng JSON.stringify và khai báo Content-Type. Khi nghi ngờ, in res.request.body ra để xem k6 gửi đi cái gì.

Hướng dẫn từng bước

Ta sẽ dựng một script hoàn chỉnh mô phỏng luồng: đăng nhập (session cookie) → lấy danh sách sản phẩm → thêm vào giỏ, cho một API dùng session cookie.

Bước 1 — Import và khai báo:

import http from 'k6/http';
import { check, sleep } from 'k6';

const BASE = 'https://api.shop.vn';

Bước 2 — Đăng nhập, server trả Set-Cookie:

export default function () {
  const loginRes = http.post(
    ${BASE}/auth/login,
    JSON.stringify({ username: 'nguyenvana', password: 'MatKhau@123' }),
    { headers: { 'Content-Type': 'application/json' } }
  );

check(loginRes, { 'login ok': (r) => r.status === 200 });

Sau bước này, cookie jar của VU đã có session cookie do server set. Các request sau trong cùng iteration tự động mang theo.

Bước 3 — Lấy danh sách sản phẩm (cookie tự đính kèm):

  const listRes = http.get(${BASE}/products?page=1, {
    tags: { name: 'ProductList' },
  });
  check(listRes, { 'list ok': (r) => r.status === 200 });

const firstId = listRes.json('data.0.id'); // lấy id sản phẩm đầu tiên

Bước 4 — Thêm vào giỏ, dùng biến động firstId:

  const cartRes = http.post(
    ${BASE}/cart/add,
    JSON.stringify({ productId: firstId, qty: 1 }),
    { headers: { 'Content-Type': 'application/json' }, tags: { name: 'CartAdd' } }
  );
  check(cartRes, { 'cart added': (r) => r.status === 201 });

sleep(1); // think-time mô phỏng người dùng thật }

Bước 5 — Chạy và kiểm tra:

k6 run --vus 10 --duration 30s script.js

Nhìn vào output, xác nhận các check đều gần 100%. Nếu cart added thấp, khả năng cao là session không được giữ — quay lại kiểm tra bước login và Content-Type.

Bước 6 (khi cần) — nếu là JWT thay vì cookie: thay bước 2 bằng bắt token và tạo authParams chứa header Authorization, rồi truyền authParams vào mọi request tiếp theo như trong Tình huống 2.

Lỗi thường gặp & mẹo

Quên JSON.stringify cho POST body. Truyền object trực tiếp → k6 gửi form-urlencoded. Chỉ dùng object trực tiếp khi backend thực sự nhận form. Với JSON: luôn stringify + set Content-Type.

Dùng http.delete thay vì http.del. delete là từ khoá reserved trong JavaScript ở một số ngữ cảnh; k6 đặt tên hàm là http.del. Nhớ kỹ để tránh lỗi cú pháp khó hiểu.

Tưởng cookie giữ qua các iteration. Không. Cookie jar reset đầu mỗi iteration để mô phỏng phiên người dùng độc lập. Nếu bạn cố tình muốn giữ token đăng nhập một lần rồi tái sử dụng cho toàn bộ test, hãy đăng nhập trong setup() (bài Setup & Teardown sẽ đi sâu) và truyền token qua data, chứ đừng trông cậy cookie jar.

Không set tags.name cho URL động. /products/1, /products/2... sẽ tạo hàng nghìn dòng metric rác. Gán tags: { name: 'ProductDetail' } để gom lại.

Không kiểm tra status thật. Latency đẹp không có nghĩa là test đúng. Luôn check(res, { 'status ok': (r) => r.status === 200 }). 401/403/500 vẫn có latency thấp.

Debug body k6 gửi đi: in res.request.bodyres.request.headers để thấy chính xác request thật. Đây là cách nhanh nhất phát hiện sai Content-Type hoặc body sai định dạng.

Redirect tự động: k6 mặc định follow tối đa 10 redirect. Nếu muốn test chính response 3xx, set redirects: 0 trong params rồi kiểm tra res.statusres.headers['Location'].

Header nhạy cảm hoa/thường tuỳ server: phần lớn server không phân biệt, nhưng khi đọc lại từ response nên dùng đúng key (res.headers['Content-Type']).

Bài tập thực hành

  • GET cơ bản + verify: Gọi một API công khai (ví dụ https://test-api.k6.io/public/crocodiles/), in res.status, res.timings.duration, và dùng res.json() để đếm số phần tử trả về. Thêm check xác nhận status 200 và mảng có ít nhất 1 phần tử.
  • POST JSON + form: Với cùng một endpoint đăng ký của https://test-api.k6.io, viết hai phiên bản: một gửi JSON (JSON.stringify + Content-Type), một gửi form (object trực tiếp). In res.request.body cả hai và so sánh sự khác biệt trong body được gửi.
  • Luồng session: Với https://test-api.k6.io, đăng nhập lấy token (đây là JWT), sau đó gọi một endpoint cần auth bằng header Authorization: Bearer. Thêm check xác nhận endpoint trả 200. Sau đó thử cố tình bỏ header auth và quan sát status đổi thành 401 — để tự tay cảm nhận sự khác biệt.
  • Cookie jar thủ công: Dùng http.cookieJar() để set thủ công một cookie giả trước khi gửi request, rồi in jar.cookiesForURL(url) để xác nhận. Quan sát cookie này biến mất ở iteration tiếp theo.

Tóm tắt

Module k6/http là nơi bạn quyết định mỗi virtual user "nói chuyện" với server ra sao — và độ chính xác của toàn bộ bài test hiệu năng phụ thuộc vào việc bạn mô phỏng request đúng như client thật. Ghi nhớ những điều cốt lõi: dùng đúng hàm method (http.get/post/put/del); với JSON body luôn JSON.stringify kèm header Content-Type: application/json; cấu hình headers, tags, timeout qua object params; đọc kết quả qua res.status, res.json(), res.timings. Về cookies, nhớ rằng k6 có cookie jar riêng cho mỗi VU và reset mỗi iteration, tự động giữ cookie trong cùng iteration — hoàn hảo cho session-based auth; còn token-based (JWT) thì bạn phải tự bắt và tự gắn header Authorization. Cuối cùng, luôn check status thật thay vì tin vào latency đẹp, và khi nghi ngờ thì in res.request.body ra để soi. Nắm vững phần này, bạn đã sẵn sàng cho các bài tiếp theo về parameterization và setup/teardown để dựng những kịch bản người dùng thực sự phức tạp.