Skip to content

2.1 - Bản đồ module: chuyện gì xảy ra khi bấm Đăng nhập ​

Tóm tắt: Module này có nhiều thuật ngữ rời rạc — DNS, TCP, TLS, HTTP, JSON, JWT, database, hosting — và học từng cái riêng lẻ rất dễ quên. Bài này là bản đồ nối tất cả lại bằng một ví dụ duy nhất chạy xuyên suốt: chuyện gì xảy ra khi bạn gõ crm.company.com và bấm Đăng nhập. Tám chặng, mỗi chặng ứng với một bài trong module. Cách học được khuyến nghị: đọc bài này trước, học từng bài, rồi quay lại đọc bài này lần nữa — lần hai bạn sẽ thấy các mảnh ghép vào đúng chỗ. Và thứ tự quan trọng: học từ trải nghiệm hằng ngày tới thuật ngữ kỹ thuật, không phải ngược lại.

Mục tiêu bài học ​

Sau bài này bạn có thể:

  • Mô tả tám chặng của một request web.
  • Biết mỗi bài trong module ứng với chặng nào.
  • Chọn thứ tự học phù hợp với nền tảng của mình.
  • Nhận ra chặng nào gây lỗi khi có sự cố.

Cần biết trước: không cần kiến thức mạng nào. Bạn cần trình duyệt Chrome hoặc Edge và một terminal. Đoạn code ở Bài 1 chạy trên API bạn đã tạo ở bài 0.5 — Web API đầu tiên.

Nội dung bài học ​

2.1.1 — Ví dụ xuyên suốt ​

Tám chặng này xảy ra trong khoảng 200 mili giây, và mỗi chặng là một bài trong module.

2.1.2 — Từng chặng, học ở bài nào ​

Chặng 1–2: gõ URL và phân giải DNS → bài 2.2

crm.company.com  ->  DNS resolver  ->  203.0.113.45

Trình duyệt không biết crm.company.com nằm ở đâu. Nó hỏi DNS — giống tra danh bạ điện thoại — để lấy địa chỉ IP.

Chặng 3: kết nối TCP → bài 2.2

Trình duyệt và server bắt tay ba bước để mở một kênh tin cậy. TCP đảm bảo dữ liệu tới đủ và đúng thứ tự.

Chặng 4: bắt tay TLS → bài 2.4

Hai bên trao đổi khoá để mã hoá. Đây là chữ S trong HTTPS. Không có nó, mật khẩu bạn gửi đi ai cũng đọc được trên đường truyền.

Chặng 5: request HTTP → bài 2.4 và bài 2.5

http
POST /api/auth/login HTTP/1.1
Host: crm.company.com
Content-Type: application/json

{ "email": "[email protected]", "password": "..." }

Đây là chỗ bạn sẽ làm việc nhiều nhất với tư cách backend engineer: method, đường dẫn, header, body, và mã trạng thái trả về.

Chặng 6: backend xử lý → bài 2.3

Server nhận request, phân tích, và quyết định làm gì. Đây là mô hình client–server: một bên hỏi, một bên trả lời.

Chặng 7: truy vấn database → bài 2.6

sql
SELECT Id, PasswordHash FROM Users WHERE Email = @email;

Backend hỏi database xem tài khoản có tồn tại không và mật khẩu có khớp không.

Chặng 8: trả JWT → bài 2.7

json
{ "token": "eyJhbGciOiJIUzI1NiIs..." }

Từ các request sau, trình duyệt đính token này vào header Authorization để server biết bạn là ai mà không phải đăng nhập lại.

Và tất cả chạy ở đâu? → bài 2.8 và bài 2.9

2.1.3 — Chặng nào gây lỗi gì ​

Bảng này hữu ích ngay cả khi bạn chưa học hết module — nó giúp khoanh vùng khi có sự cố:

Triệu chứngChặng có vấn đề
ERR_NAME_NOT_RESOLVEDDNS (chặng 2)
ERR_CONNECTION_REFUSEDTCP — server không chạy hoặc sai cổng (chặng 3)
ERR_CERT_AUTHORITY_INVALIDTLS — chứng chỉ sai hoặc hết hạn (chặng 4)
404 Not FoundHTTP — sai đường dẫn (chặng 5)
500 Internal Server ErrorBackend — lỗi trong code (chặng 6)
Timeout khi gọi APIDatabase chậm hoặc mất kết nối (chặng 7)
401 UnauthorizedToken thiếu, sai hoặc hết hạn (chặng 8)

Nguyên tắc chẩn đoán: mã lỗi cho biết chặng nào, và biết chặng nào tiết kiệm rất nhiều thời gian tìm kiếm (bài 2.11).

2.1.4 — Thứ tự học theo nền tảng ​

Nếu bạn hoàn toàn mới với web: đọc theo đúng thứ tự 2.2 → 2.9. Mỗi bài dựa trên bài trước.

Nếu đã biết HTML/CSS/JavaScript: bắt đầu từ 2.4 (HTTP) và 2.5 (API và JSON) — hai bài gần với công việc backend nhất. Quay lại 2.2 và 2.3 khi cần.

Nếu đã làm frontend: 2.6 (database) và 2.7 (xác thực) là hai mảng bạn ít tiếp xúc nhất. Ưu tiên chúng.

Nếu chỉ có 2 giờ: 2.4 và 2.7. Hai bài này chiếm phần lớn công việc hàng ngày của một backend engineer.

2.1.5 — Học từ trải nghiệm tới thuật ngữ ​

Cách học không hiệu quả:

Học thuộc: "DNS là hệ thống phân giải tên miền thành địa chỉ IP"
-> Nhớ được vài ngày, rồi quên

Cách học hiệu quả:

1. Mở terminal, gõ:  nslookup example.com
2. Thấy kết quả trả về một vài địa chỉ IP
3. Gõ một địa chỉ IP đó vào trình duyệt
   -> thường KHÔNG ra trang example.com: báo lỗi 403 hoặc cảnh báo chứng chỉ
4. "À, DNS chỉ đưa mình tới đúng máy. Chọn trang nào trên máy đó là chuyện khác"
-> Hiểu, và không quên

Bước 3 không ra trang như bạn tưởng, và chính chỗ bất ngờ đó làm bạn nhớ lâu. Bài 2 bên dưới giải thích vì sao.

Với mỗi khái niệm trong module, tìm một cách tự tay thử nó. Vài lệnh đáng biết ngay:

bash
nslookup crm.company.com          # DNS trả về IP nào
curl -i https://example.com       # Xem đầy đủ header phản hồi
ping google.com                   # Đo độ trễ mạng

Và công cụ quan trọng nhất: DevTools của trình duyệt, tab Network. Mọi thứ trong module này đều quan sát được ở đó — request, header, mã trạng thái, thời gian từng chặng.

2.1.6 — Những gì cần đạt sau module ​

Danh sách rà soát trước khi sang Module 3

  • Mô tả được tám chặng của một request web.
  • Biết mã lỗi nào ứng với chặng nào.
  • Đã tự chạy nslookup và curl ít nhất một lần.
  • Mở được DevTools tab Network và đọc được một request.
  • Phân biệt được HTTP và HTTPS khác nhau ở đâu.
  • Giải thích được JWT dùng để làm gì.
  • Biết database nằm ở chặng nào trong luồng.

Bài tập áp dụng ​

Bài 1 — Lần theo luồng thật ​

Mở DevTools trên một trang có đăng nhập, thực hiện đăng nhập, và xác định từng chặng trong tab Network.

Tiêu chí hoàn thành: bạn ánh xạ được tám chặng vào những gì DevTools hiện ra, và nhận ra có chặng không hiện ra ở đó.

Gợi ý và lời giải — Bài 1

Gợi ý. Bấm vào một request rồi mở tab Timing. Nó chia sẵn thời gian theo từng giai đoạn.

Lời giải — đọc bảng Timing của request đăng nhập (số liệu minh họa, máy và mạng của bạn sẽ cho số khác):

text
POST https://crm.company.com/api/auth/login

Queueing                    0,42 ms
Stalled                     1,18 ms
DNS Lookup                 24,00 ms     <- chặng 2
Initial connection         48,00 ms     <- chặng 3 (TCP)
  SSL                      31,00 ms     <- chặng 4 (TLS, nằm trong Initial connection)
Request sent                0,21 ms     <- chặng 5
Waiting (TTFB)            186,00 ms     <- chặng 6 và 7
Content Download           12,00 ms     <- chặng 8
                          ---------
Total                     271,81 ms

Ánh xạ tám chặng:

ChặngTrong DevToolsĐo được
1. Gõ URLQueueing, Stalled1,6 ms
2. DNSDNS Lookup24 ms
3. TCPInitial connection trừ SSL17 ms
4. TLSSSL31 ms
5. HTTP requestRequest sent0,2 ms
6. Backend xử lýWaiting (TTFB)186 ms
7. Databasecũng nằm trong Waitingkhông tách được
8. Trả JWTContent Download12 ms

Và đây là điều bài tập muốn bạn thấy: chặng 7 KHÔNG hiện ra riêng.

text
DevTools chỉ thấy tới biên của máy chủ.
Nó biết "máy chủ mất 186 ms", nhưng không biết trong 186 ms đó:
    - bao nhiêu cho middleware xác thực
    - bao nhiêu cho truy vấn database
    - bao nhiêu cho việc băm mật khẩu
text
Băm mật khẩu đáng chú ý: BCrypt hoặc Argon2 CỐ TÌNH chậm,
thường 50–200 ms, để chống dò mật khẩu bằng vét cạn.
-> với endpoint đăng nhập, phần lớn TTFB có thể là băm mật khẩu,
   không phải database.

Muốn tách chặng 6 và 7, phải đo từ phía máy chủ. Thêm đoạn sau vào Program.cs của API ở bài 0.5, chia làm hai chỗ: hai dòng using đặt ở đầu file, còn app.Use(...) đặt ngay sau dòng var app = builder.Build();. Đây là một middleware: đoạn code chạy với mọi request trước và sau endpoint. Bài 8.2 dạy kỹ.

csharp
using System.Diagnostics;      // Stopwatch
using System.Globalization;    // CultureInfo

// ... phần code sẵn có ...
// var app = builder.Build();   <- đoạn dưới đặt ngay sau dòng này

app.Use(async (ctx, next) =>
{
    var sw = Stopwatch.StartNew();

    // Header phải gắn TRƯỚC khi response bắt đầu gửi đi.
    // OnStarting chạy đúng lúc đó, khi endpoint đã xử lý xong.
    ctx.Response.OnStarting(() =>
    {
        ctx.Response.Headers["Server-Timing"] = string.Create(
            CultureInfo.InvariantCulture, $"app;dur={sw.Elapsed.TotalMilliseconds:F1}");
        return Task.CompletedTask;
    });

    await next();
});

Gọi curl -i tới một endpoint, kết quả chạy thật trên .NET 10 (rút gọn, bỏ vài header):

text
HTTP/1.1 200 OK
Content-Type: text/plain; charset=utf-8
Server: Kestrel
Server-Timing: app;dur=3.0

Hai lỗi hay gặp khi tự viết đoạn này:

csharp
// SAI: gán header SAU await next()
app.Use(async (ctx, next) =>
{
    var sw = Stopwatch.StartNew();
    await next();
    ctx.Response.Headers["Server-Timing"] = $"app;dur={sw.Elapsed.TotalMilliseconds:F1}";
});
text
System.InvalidOperationException: Headers are read-only, response has already started.
  • Endpoint đã ghi body thì header đã được gửi đi trước body, không sửa được nữa. Client vẫn nhận 200 nhưng không có Server-Timing, còn log máy chủ ghi ngoại lệ trên.
  • Không có CultureInfo.InvariantCulture, máy đặt định dạng tiếng Việt sẽ in app;dur=0,1 với dấu phẩy. Trình duyệt không hiểu giá trị đó.
text
Header Server-Timing hiện THẲNG trong tab Timing của DevTools
-> nối được số liệu máy chủ với số liệu trình duyệt

Hai điều nữa quan sát được trong cùng tab Network:

1. Request thứ hai nhanh hơn hẳn.

text
Request 1: DNS 24 ms + connection 48 ms = 72 ms chi phí thiết lập
Request 2: DNS 0 ms + connection 0 ms   = 0 ms

-> DNS đã nằm trong bộ nhớ đệm, kết nối được dùng lại (keep-alive)

2. Token đi đâu sau khi nhận được.

text
Xem request TIẾP THEO sau khi đăng nhập:
    Authorization: Bearer eyJhbGciOiJIUzI1NiIs...

Hoặc, nếu dùng cookie:
    Set-Cookie: .AspNetCore.Identity.Application=...; HttpOnly; Secure; SameSite=Lax
text
HttpOnly là khác biệt quan trọng: JavaScript KHÔNG đọc được cookie đó
-> một lỗ hổng XSS không lấy được phiên đăng nhập
-> token lưu trong localStorage thì ngược lại

Bài 2 — Thử DNS bằng tay ​

Chạy nslookup cho ba tên miền bất kỳ, rồi thử truy cập bằng IP thay vì tên miền.

Tiêu chí hoàn thành: bạn thấy truy cập bằng IP trần không ra đúng trang, và giải thích được thứ gì mới thật sự chọn ra trang web.

Gợi ý và lời giải — Bài 2

Gợi ý. Một địa chỉ IP thường phục vụ hàng nghìn tên miền. Máy chủ dựa vào đâu để biết bạn muốn trang nào?

Lời giải — tra DNS:

bash
for d in example.com github.com microsoft.com; do
  echo "--- $d"; nslookup $d | grep -A3 "Non-authoritative"
done

Kết quả chạy thật:

text
--- example.com
Non-authoritative answer:
Name:	example.com
Address: 172.66.147.243
Name:	example.com
Address: 104.20.23.154

--- github.com
Non-authoritative answer:
Name:	github.com
Address: 20.205.243.166

--- microsoft.com
Non-authoritative answer:
Name:	microsoft.com
Address: 150.171.110.68
Name:	microsoft.com
Address: 2603:1061:14:16b::1

Ba quan sát ngay từ đây:

text
1. example.com có HAI địa chỉ IPv4 -> cân bằng tải ở tầng DNS
2. microsoft.com có cả IPv4 lẫn IPv6 (bản ghi A và AAAA)
3. "Non-authoritative answer" nghĩa là câu trả lời lấy từ bộ nhớ đệm
   của resolver, không phải hỏi thẳng máy chủ tên miền gốc

Giờ thử truy cập bằng IP:

bash
curl -s -o /dev/null -w "IP trần: %{http_code}\n" http://172.66.147.243/
curl -s -o /dev/null -w "IP + Host header: %{http_code}\n" -H "Host: example.com" http://172.66.147.243/
curl -sk -o /dev/null -w "HTTPS bằng IP (bỏ qua chứng chỉ): %{http_code}\n" \
     --resolve example.com:443:172.66.147.243 https://example.com/
curl -s -o /dev/null -w "HTTPS bằng IP trực tiếp: %{http_code} ssl=%{ssl_verify_result}\n" \
     https://172.66.147.243/

Kết quả chạy thật:

text
IP trần:                          403
IP + Host header:                 200
HTTPS bằng IP (bỏ qua chứng chỉ): 200
HTTPS bằng IP trực tiếp:          000 ssl=1

Bốn dòng này trả lời câu hỏi chính:

text
IP trần -> 403
    Máy chủ nhận được request nhưng KHÔNG BIẾT bạn muốn trang nào
    -> nó từ chối

IP + Host: example.com -> 200
    Header Host là thứ CHỌN RA trang web
text
Nói cách khác: DNS chỉ đưa bạn TỚI ĐÚNG MÁY.
               Header Host mới nói bạn muốn TRANG NÀO trên máy đó.
http
GET / HTTP/1.1
Host: example.com          <- dòng này chọn trang web
text
Một máy chủ có thể phục vụ hàng nghìn tên miền trên cùng một IP.
Đây gọi là virtual hosting, và header Host là cơ chế duy nhất phân biệt chúng.

Và dòng thứ tư nói về HTTPS — nó còn chặt hơn:

text
https://172.66.147.243/ -> ssl_verify_result=1, kết nối THẤT BẠI

Chứng chỉ được cấp cho TÊN MIỀN example.com, không phải cho một địa chỉ IP.
-> trình duyệt so tên trong chứng chỉ với tên bạn gõ -> không khớp -> từ chối
text
Còn --resolve example.com:443:172.66.147.243 thì thành công, vì:
    - curl vẫn gửi tên example.com trong SNI và trong header Host
    - nó chỉ bỏ qua bước hỏi DNS, dùng IP bạn chỉ định
-> chứng chỉ khớp -> 200

SNI — Server Name Indication — là phiên bản của header Host ở tầng TLS:

text
Vấn đề: bắt tay TLS xảy ra TRƯỚC khi gửi header HTTP
        -> máy chủ phải chọn chứng chỉ nào mà chưa biết bạn muốn trang gì

SNI giải quyết: tên miền được gửi kèm ngay trong bước bắt tay TLS
-> máy chủ chọn đúng chứng chỉ

Và vì nó nằm ngoài phần mã hoá, SNI là thứ duy nhất trong một kết nối HTTPS
mà người quan sát trên đường truyền đọc được — họ biết bạn vào trang nào,
nhưng không biết bạn xem gì trên đó.

Ba lệnh nữa đáng biết khi gỡ lỗi DNS:

bash
dig +short crm.company.com              # gọn hơn nslookup
dig crm.company.com +trace              # xem toàn bộ chuỗi phân giải từ gốc
dig @8.8.8.8 crm.company.com            # hỏi một resolver KHÁC
text
Lệnh thứ ba hữu ích nhất khi gặp "trang này chạy ở máy tôi mà không chạy ở máy anh":
    hai máy dùng hai resolver khác nhau, và một cái còn giữ bản ghi CŨ trong đệm
-> so sánh kết quả từ hai resolver là cách nhanh nhất xác nhận giả thuyết đó

Bài 3 — Tự vẽ sơ đồ ​

Không nhìn bài, vẽ lại tám chặng và ghi tên bài học tương ứng với mỗi chặng.

Tiêu chí hoàn thành: bạn vẽ được đủ tám chặng, và với mỗi chặng nêu được một lỗi cụ thể có thể xảy ra ở đó.

Gợi ý và lời giải — Bài 3

Gợi ý. Nhớ được tám chặng là một chuyện. Biết chặng nào gây lỗi gì mới là thứ dùng được khi có sự cố.

Lời giải — sơ đồ kèm lỗi đặc trưng của từng chặng:

text
1. Gõ URL
   Lỗi: gõ sai, hoặc trình duyệt tự thêm https:// vào một địa chỉ chỉ có http

2. DNS: tên miền -> IP                          [bài 2.2]
   Lỗi: ERR_NAME_NOT_RESOLVED
        tên miền hết hạn, bản ghi sai, hoặc resolver còn giữ bản ghi cũ
   Chẩn đoán: dig +short, dig @8.8.8.8

3. TCP: thiết lập kết nối                       [bài 2.2]
   Lỗi: ERR_CONNECTION_REFUSED  -> không có gì lắng nghe ở cổng đó
        ERR_CONNECTION_TIMED_OUT -> tường lửa chặn, hoặc máy chủ không phản hồi
   Chẩn đoán: telnet <host> 443, hoặc nc -zv <host> 443

4. TLS: mã hoá kênh truyền                      [bài 2.2, 2.4]
   Lỗi: ERR_CERT_DATE_INVALID      -> chứng chỉ hết hạn
        ERR_CERT_COMMON_NAME_INVALID -> chứng chỉ cấp cho tên khác
   Chẩn đoán: openssl s_client -connect host:443 -servername host

5. HTTP: POST /api/auth/login                   [bài 2.4, 2.5]
   Lỗi: 400 body sai định dạng
        404 sai đường dẫn
        405 sai method
        415 thiếu Content-Type
   Chẩn đoán: đọc thẳng tab Network, hoặc curl -v

6. Backend nhận và xử lý                        [bài 2.3]
   Lỗi: 500 ngoại lệ chưa xử lý
        502 gateway không gọi được ứng dụng
        503 ứng dụng chưa sẵn sàng
   Chẩn đoán: log của ứng dụng, và traceId trong phản hồi

7. Database: kiểm tra tài khoản                 [bài 2.6]
   Lỗi: timeout, pool cạn, truy vấn chậm
        -> thường biểu hiện ra ngoài thành 500 hoặc 504
   Chẩn đoán: log truy vấn, thời gian chờ lấy kết nối

8. Trả JWT về trình duyệt                       [bài 2.7]
   Lỗi: 401 ở các request SAU (token sai, hết hạn, lệch đồng hồ)
        lỗi CORS nếu web và API khác origin
   Chẩn đoán: giải mã token, so exp với giờ hiện tại

Và đây là cách dùng sơ đồ này khi có sự cố — chia đôi thay vì đi tuần tự:

text
Thay vì kiểm tra chặng 1, rồi 2, rồi 3...
hãy kiểm tra chặng 5 TRƯỚC:

    curl -i https://crm.company.com/api/auth/login -d '...'

Có phản hồi HTTP -> chặng 1–5 đều tốt -> vấn đề ở 6, 7 hoặc 8
Không có         -> vấn đề ở 1–4
text
Một lệnh loại bỏ được một nửa số khả năng.

Đây là cùng ý tưởng với git bisect ở bài 3.9: chia đôi phạm vi tìm kiếm sau mỗi lần thử.

Bảng ánh xạ nhanh từ triệu chứng sang chặng:

Triệu chứngChặngKiểm tra đầu tiên
Trang không mở được, không có gì trong Network2–3dig, nc -zv
Cảnh báo bảo mật của trình duyệt4openssl s_client
Có mã trạng thái 4xx5Đọc body phản hồi
Có mã trạng thái 5xx6–7Log máy chủ theo traceId
Đăng nhập được rồi lại bị đẩy ra8Giải mã token, xem exp
"Blocked by CORS policy"5Header phản hồi, và thứ tự middleware

Và một bài tập nhỏ để tự kiểm tra: giải thích cho người không làm kỹ thuật.

text
"Khi anh gõ địa chỉ và bấm Enter:
 máy tính hỏi danh bạ xem trang đó nằm ở máy nào,
 gọi điện tới máy đó và thống nhất một cách nói chuyện riêng để không ai nghe lén,
 gửi câu hỏi kèm tên đăng nhập và mật khẩu,
 máy bên kia tra sổ xem có đúng không,
 rồi đưa lại một tấm vé để lần sau khỏi phải đăng nhập nữa."
text
Giải thích được bằng ngôn ngữ đó nghĩa là bạn đã hiểu, không phải đã thuộc.
Và đó cũng là kỹ năng cần khi phải nói chuyện với người dùng trong một sự cố.

Tự kiểm tra ​

Vì sao nên học từ trải nghiệm tới thuật ngữ?

Vì học thuộc định nghĩa thì quên sau vài ngày, còn tự tay chạy nslookup rồi thử gõ IP vào trình duyệt thì thấy ngay DNS làm được gì và không làm được gì, và nhớ lâu hơn. Mỗi khái niệm nên có một cách tự thử.

Tám chặng của một request web là gì?

Gõ URL, phân giải DNS, thiết lập kết nối TCP, bắt tay TLS, gửi request HTTP, backend xử lý, truy vấn database, và trả kết quả kèm token về trình duyệt.

Mã lỗi ERR_CONNECTION_REFUSED cho biết vấn đề ở chặng nào?

Chặng TCP. Tên miền đã phân giải ra IP thành công nhưng không mở được kết nối, thường vì server không chạy hoặc sai cổng.

Lỗi 401 khác lỗi 404 ở chỗ nào về mặt chẩn đoán?

404 là sai đường dẫn ở chặng HTTP, tức route không tồn tại. 401 là vấn đề ở chặng xác thực, tức token thiếu, sai hoặc hết hạn, còn đường dẫn thì đúng.

Nếu chỉ có hai giờ thì nên học bài nào?

Bài về HTTP và bài về xác thực, vì hai mảng đó chiếm phần lớn công việc hàng ngày của một backend engineer.

Công cụ quan trọng nhất để quan sát mọi thứ trong module này là gì?

DevTools của trình duyệt, tab Network. Ở đó bạn thấy được request, header, mã trạng thái và thời gian của từng chặng.

Kết luận ​

Ba điều đáng nhớ nhất:

  1. Một ví dụ xuyên suốt nối mọi khái niệm lại — dễ nhớ hơn học từng thuật ngữ rời.
  2. Mã lỗi cho biết chặng nào có vấn đề. Đây là kỹ năng chẩn đoán cơ bản nhất.
  3. Tự tay thử mỗi khái niệm. Một lệnh nslookup đáng giá hơn một đoạn định nghĩa.

Tham khảo ​

Điều hướng ​

Phát hành theo giấy phép MIT.