Thuta Learning
Computer Networking
IntermediateDevOps & Toolsbeginner

HTTP Request Lifecycle

ဒီခန်းပြီးရင် ဘာတတ်သွားမလဲ

  • HTTP Request Lifecycle concept ကို နားလည်ရှင်းပြနိုင်ရန်
  • Diagram ကို ဖတ်ပြီး packet/data ဘယ်လိုသွားလာသလဲ ခြေရာခံနိုင်ရန်
  • နမူနာ code ကို ကိုယ်တိုင် run ပြီး output စစ်နိုင်ရန်

နားလည်ထားရမယ့် အချက်

HTTP/1.1 ဟာ TCP connection တစ်ခုပေါ်မှာ ပို့လိုက်တဲ့ စာသားတစ်ခုမျှသာ ဖြစ်တယ် ဆိုတာကို မြင်လိုက်တာနဲ့ အများကြီး ရှင်းသွားတယ်။ request တစ်ခုမှာ method, path နဲ့ version ပါတဲ့ request line တစ်ကြောင်း၊ 'Name: value' ပုံစံ header အကြောင်းရေ ဘယ်လောက်မဆို၊ ကွက်လပ်တစ်ကြောင်း၊ ပြီးမှ body လာတယ်။ ကြောင်းတိုင်းကို CR နဲ့ LF နှစ်လုံးနဲ့ ပိတ်ရပြီး၊ ကွက်လပ်တစ်ကြောင်း (CRLF CRLF) ကသာ 'header ပြီးပြီ' လို့ ပြောပြတဲ့ တစ်ခုတည်းသော အမှတ်အသား ဖြစ်တယ်။ body ဘယ်လောက် ရှည်လဲ ဆိုတာကို Content-Length (သို့) Transfer-Encoding: chunked ကပဲ ပြောပြရတယ် — ဒါမှမဟုတ်ရင် receiver ဟာ connection ပိတ်တဲ့အထိ ဘယ်တော့ပြီးမှန်း မသိဘူး။

status code ကို အလွတ်ကျက်စရာ မလိုဘဲ မိသားစုအလိုက် မှတ်ပါ — 2xx က အောင်မြင်တယ်၊ 3xx က တခြားနေရာမှာ ကြည့်ပါ၊ 4xx က ပို့လိုက်တဲ့ request မှားနေတယ် ဆိုတော့ အတူတူ ထပ်ပို့ရင်လည်း အတူတူပဲ ဖြစ်မယ်၊ 5xx က server ဘက်မှာ ပျက်နေတဲ့အတွက် ခဏနေမှ ထပ်ကြိုးစားတာ ဆီလျော်တယ်။ ဒီကွာခြားချက်က retry logic ကို ဆုံးဖြတ်ပေးတယ်။

အရေးအကြီးဆုံး ဒီဇိုင်း ဆုံးဖြတ်ချက်ကတော့ HTTP ဟာ stateless ဖြစ်တာ ဖြစ်တယ် — server ဟာ request တစ်ခုကို အဲဒီ request ထဲမှာ ပါလာတဲ့ အချက်အလက်တွေနဲ့ပဲ နားလည်ရမယ် လို့ သတ်မှတ်ထားတာ ဖြစ်တယ်။ ဒါက server တွေကို လွယ်လွယ်ကူကူ အများအပြား ချဲ့နိုင်စေတယ် — request ဘယ် server ဆီ ရောက်ရောက် အလုပ်လုပ်တယ်။ ဒါပေမယ့် login လိုမျိုး အရာတွေအတွက် တစ်ခုခု လိုအပ်လာလို့ cookie ကို ထည့်ခဲ့တာ ဖြစ်တယ် — server က Set-Cookie နဲ့ တန်ဖိုးတစ်ခု ပေးလိုက်ပြီး client က နောက် request တိုင်းမှာ ပြန်ပါလာစေတယ်။ state ဟာ connection ထဲမှာ မရှိဘဲ request တိုင်းမှာ ပါလာနေတာ ဖြစ်လို့ stateless သဘောသဘာဝ မပျက်ဘူး ဆိုတာ သတိထားပါ။ keep-alive က တခြားပြဿနာကို ဖြေရှင်းတယ် — request တစ်ခုစီအတွက် TCP connection အသစ် ဖွင့်နေရတာကို ရပ်ပြီး connection တစ်ခုကို ပြန်သုံးစေတယ်။ HTTP/1.1 မှာ connection တစ်ခုပေါ်မှာ request တွေက တစ်ခုပြီးမှ တစ်ခု စီတန်းရလို့ head-of-line blocking ဖြစ်နေဆဲ ဖြစ်တယ်။ HTTP/2 က message တွေကို binary frame တွေအဖြစ် ခွဲပြီး connection တစ်ခုတည်းပေါ်မှာ stream များစွာကို ရောပြီး ပို့နိုင်စေတာကြောင့် response တစ်ခု နှေးနေတာက ကျန်တာတွေကို မပိတ်ဆို့တော့ဘူး။

text
ANATOMY OF A RAW HTTP REQUEST AND RESPONSE
------------------------------------------
              REQUEST                        RESPONSE
            +---------------------------+  +---------------------------+
start line  | POST /api/signup HTTP/1.1 |  | HTTP/1.1 201 Created      |
            +---------------------------+  +---------------------------+
headers     | Host: shop.example.test   |  | Content-Type: app/json    |
            | Content-Type: app/json    |  | Content-Length: 28        |
            | Content-Length: 27        |  | Set-Cookie: session=xyz   |
            | Cookie: session=abc123    |  | Connection: keep-alive    |
            +---------------------------+  +---------------------------+
blank line  |      CRLF CRLF            |  |      CRLF CRLF            |
            +---------------------------+  +---------------------------+
body        | {"email":"ko@ex.test"}    |  | {"id":42,"status":"ok"}   |
            +---------------------------+  +---------------------------+

လက်တွေ့ scenario နဲ့ ချိတ်ကြည့်မယ်

checkout API ကို ခေါ်တဲ့အခါ user တစ်ချို့ဟာ order နှစ်ခါ တင်မိနေတယ် ဆိုတဲ့ bug တစ်ခုကို စစ်ကြည့်ရအောင်။ log ထဲမှာ မြင်ရတာက mobile client က POST /orders ကို ပို့ပြီး အဖြေ မရလို့ retry လုပ်၊ server ဘက်မှာတော့ ပထမ request က အောင်မြင်ခဲ့ပြီးသား ဖြစ်နေတာ ဖြစ်တယ်။ ဒီနေရာမှာ status code မိသားစုတွေက အဖြေပေးတယ်။ client ရဲ့ retry logic က 'အဖြေ မရရင် ပြန်ပို့' လို့ ရေးထားပြီး၊ ဒါက timeout ဖြစ်တဲ့အခါ request ရောက်မရောက် မသိဘဲ ပြန်ပို့နေတာ ဖြစ်တယ်။ 4xx ကို retry မလုပ်ဘူး၊ 5xx ကို retry လုပ်တယ် ဆိုတဲ့ စည်းမျဉ်းက မှန်ပေမယ့် timeout က status code မဟုတ်လို့ ဒီစည်းမျဉ်းအောက် မကျဘူး။

ဖြေရှင်းနည်းက request ကိုယ်တိုင်ကို idempotent ဖြစ်အောင် လုပ်ဖို့ ဖြစ်တယ် — client က order တစ်ခုစီအတွက် တစ်မူထူးခြားတဲ့ Idempotency-Key header တစ်ခု ထုတ်ပြီး ပါလာစေတယ်၊ server က ဒီ key ကို မြင်ဖူးပြီးသားဆိုရင် အလုပ်ကို ထပ်မလုပ်ဘဲ မူလ response ကို ပြန်ပေးလိုက်တယ်။ ဒါက stateless သဘောသဘာဝနဲ့ ကိုက်ညီတယ် — retry လုပ်တဲ့ request ဟာ သူ့ကိုယ်သူ ရှင်းပြနိုင်တဲ့ အချက်အလက်ကို ကိုယ်တိုင် သယ်လာတယ်။ ဒီလို bug မျိုးကို စစ်တဲ့အခါ browser ရဲ့ network tab က header တွေကို သန့်ရှင်းအောင် ပြင်ပြထားလို့ raw bytes ကို မမြင်ရဘူး ဆိုတာ သတိထားပါ — Content-Length မှားနေတာ (သို့) header နှစ်ခါ ပါနေတာမျိုးက အဲဒီမှာ ပျောက်နေတတ်ပြီး wire ပေါ်မှာမှ ပေါ်တတ်တယ်။

အတူတူ စမ်းရေးကြည့်မယ်

python
CRLF = "\r\n"

# ---- 1. Build a raw HTTP/1.1 request exactly as it goes on the wire -----
body = '{"email":"ko@example.test"}'
lines = [
    "POST /api/signup HTTP/1.1",
    "Host: shop.example.test",
    "User-Agent: hand-rolled/1.0",
    "Content-Type: application/json",
    "Content-Length: %d" % len(body.encode()),
    "Connection: keep-alive",
    "Cookie: session=abc123",
]
request = CRLF.join(lines) + CRLF + CRLF + body

print("--- request on the wire (CRLF shown as \\r\\n) ---")
for line in request.split(CRLF):
    print(repr(line)[1:-1] + ("\\r\\n" if line != body else ""))
print("request bytes  :", len(request.encode()))

# ---- 2. Parse a raw HTTP/1.1 response ----------------------------------
raw = (
    "HTTP/1.1 201 Created" + CRLF +
    "Content-Type: application/json" + CRLF +
    "Content-Length: 28" + CRLF +
    "Set-Cookie: session=xyz789; HttpOnly" + CRLF +
    "Connection: keep-alive" + CRLF +
    CRLF +
    '{"id":42,"status":"created"}'
)

FAMILIES = {
    1: "informational", 2: "success", 3: "redirection",
    4: "client error", 5: "server error",
}

head, _, resp_body = raw.partition(CRLF + CRLF)
status_line, *header_lines = head.split(CRLF)
version, code, reason = status_line.split(" ", 2)

headers = {}
for line in header_lines:
    name, _, value = line.partition(": ")
    headers[name.lower()] = value

print()
print("--- parsed response ---")
print("version        :", version)
print("status         : %s %s (%s)"
      % (code, reason, FAMILIES[int(code) // 100]))
print("headers parsed :", len(headers))
for name in sorted(headers):
    print("  %-14s %s" % (name + ":", headers[name]))
print("body           :", resp_body)

declared = int(headers["content-length"])
print("content-length : declared %d, actual %d, match=%s"
      % (declared, len(resp_body.encode()), declared == len(resp_body.encode())))
print("connection     :", headers["connection"],
      "-> socket stays open for the next request")
You should see
--- request on the wire (CRLF shown as \r\n) ---
POST /api/signup HTTP/1.1\r\n
Host: shop.example.test\r\n
User-Agent: hand-rolled/1.0\r\n
Content-Type: application/json\r\n
Content-Length: 27\r\n
Connection: keep-alive\r\n
Cookie: session=abc123\r\n
\r\n
{"email":"ko@example.test"}
request bytes  : 210

--- parsed response ---
version        : HTTP/1.1
status         : 201 Created (success)
headers parsed : 4
  connection:    keep-alive
  content-length: 28
  content-type:  application/json
  set-cookie:    session=xyz789; HttpOnly
body           : {"id":42,"status":"created"}
content-length : declared 28, actual 28, match=True
connection     : keep-alive -> socket stays open for the next request

၅ မိနစ် စမ်းကြည့်

response ထဲက Content-Length ကို 28 ကနေ 12 အဖြစ် ပြောင်းပြီး run ကြည့်ပါ — code က ဘယ်လို ပြသလဲ၊ တကယ့် HTTP client တစ်ခုဆိုရင် ဘာဖြစ်မလဲ။ ပြီးရင် request ထဲမှာ Host header ကို ဖျက်လိုက်ပြီး HTTP/1.1 မှာ Host က ဘာကြောင့် မဖြစ်မနေ လိုအပ်သလဲ စဉ်းစားပါ။

သတိလေးတစ်ချက်

line ending အဖြစ် \n တစ်လုံးတည်း သုံးပြီး raw request ရေးတာ။ HTTP က CRLF ကို သတ်မှတ်ထားပြီး server တစ်ချို့က လက်ခံပေမယ့် proxy တစ်ချို့က ငြင်းပယ်တယ်။

keep-alive ကို multiplexing လို့ မှတ်တာ။ HTTP/1.1 connection တစ်ခုပေါ်မှာ request တွေက စီတန်းနေဆဲ ဖြစ်ပြီး၊ အပြိုင် ပို့နိုင်တာက HTTP/2 ကမှ စတယ်။

RFC 9110 - HTTP SemanticsComputer Networking

ဒီနေရာမှာ လူအများမှားတတ်တယ်

  • line ending အဖြစ် \n တစ်လုံးတည်း သုံးပြီး raw request ရေးတာ။ HTTP က CRLF ကို သတ်မှတ်ထားပြီး server တစ်ချို့က လက်ခံပေမယ့် proxy တစ်ချို့က ငြင်းပယ်တယ်။
  • keep-alive ကို multiplexing လို့ မှတ်တာ။ HTTP/1.1 connection တစ်ခုပေါ်မှာ request တွေက စီတန်းနေဆဲ ဖြစ်ပြီး၊ အပြိုင် ပို့နိုင်တာက HTTP/2 ကမှ စတယ်။
  • နမူနာ code ကို production network ပေါ် တိုက်ရိုက်မစမ်းဘဲ local/test environment တွင် အရင်အတည်ပြုပါ။

လေ့ကျင့်ခန်း

response ထဲက Content-Length ကို 28 ကနေ 12 အဖြစ် ပြောင်းပြီး run ကြည့်ပါ — code က ဘယ်လို ပြသလဲ၊ တကယ့် HTTP client တစ်ခုဆိုရင် ဘာဖြစ်မလဲ။ ပြီးရင် request ထဲမှာ Host header ကို ဖျက်လိုက်ပြီး HTTP/1.1 မှာ Host က ဘာကြောင့် မဖြစ်မနေ လိုအပ်သလဲ စဉ်းစားပါ။

You'll know it worked when: --- request on the wire (CRLF shown as \r\n) --- POST /api/signup HTTP/1.1\r\n Host: shop.example.test\r\n User-Agent: hand-rolled/1.0\r\n Content-Type: application/json\r\n Content-Length: 27\r\n Connection: keep-alive\r\n Cookie: session=abc123\r\n \r\n {"email":"ko@example.test"} request bytes : 210 --- parsed response --- version : HTTP/1.1 status : 201 Created (success) headers parsed : 4 connection: keep-alive content-length: 28 content-type: application/json set-cookie: session=xyz789; HttpOnly body : {"id":42,"status":"created"} content-length : declared 28, actual 28, match=True connection : keep-alive -> socket stays open for the next request

HTTP Request Lifecycle | Thuta Learning