Bỏ qua điều hướng, tới nội dung chính
Học C
Bài 60.630 phút đọc

Level 6: Máy chủ HTTP nhỏ

Sau bài này bạn sẽ làm được

  • Phân tích yêu cầu HTTP: phương thức, đường dẫn, tiêu đề
  • Phục vụ tệp tĩnh với đúng Content-Type và mã trạng thái
  • Chống duyệt đường dẫn một cách bắt buộc
  • Kiểm thử bằng trình duyệt thật và công cụ đo tải

Máy chủ HTTP nhỏ là dự án đầu tiên nói giao thức của một hệ thống thật: bạn phân tích một yêu cầu HTTP, tìm tệp, và trả lời đúng chuẩn để trình duyệt thật hiển thị được. Nó ghép Phần 16, 17, và bài học bảo mật của Phần 20.

#Mục tiêu

terminal
curl -i http://localhost:8080/index.html
HTTP/1.1 200 OK
Content-Type: text/html
Content-Length: 137
Connection: keep-alive

<!doctype html><html>...
Chức năngKỹ thuậtBài
Nhận kết nối, đọc yêu cầusocket, accept, recv46.3, 46.6
Phân tích HTTPTách dòng, phân cách bằng CRLF46.5
Phục vụ tệp tĩnhopen, đọc, gửi kèm Content-TypeChương 41
Đồng thờiepoll hoặc bể luồng46.8, 46.9
Chống duyệt đường dẫnrealpath, kiểm tra tiền tố59.3
Ghi logCommon Log Format56.4

#Phân tích HTTP

Cấu trúc một yêu cầu HTTP
GET /index.html HTTP/1.1\r\n          <- dong yeu cau: phuong thuc, duong dan, phien ban
Host: localhost:8080\r\n            <- cac dong tieu de
User-Agent: curl/7.68\r\n
Accept: */*\r\n
\r\n                                 <- DONG RONG: het tieu de
(than, neu la POST)

/* Ba phan tach nhau ro rang:
   1. Dong yeu cau: mot dong, ba truong cach nhau bang dau cach.
   2. Tieu de: nhieu dong "Ten: Gia tri", toi khi gap DONG RONG.
   3. Than: co Content-Length byte, chi POST/PUT co.

   HTTP dung CA HAI cach danh ranh gioi cua Bai 46.5:
     - tieu de: phan cach bang dong (doc toi dong rong)
     - than: tien to do dai (Content-Length noi bao nhieu byte) */
  1. Đọc tới dòng trống

    Đọc vào bộ đệm tích luỹ cho tới khi gặp \r\n\r\n. Đó là ranh giới hết phần tiêu đề. Đừng giả định cả yêu cầu tới trong một lần recv.
  2. Tách dòng yêu cầu

    Ba trường: phương thức (GET), đường dẫn (/index.html), phiên bản (HTTP/1.1).
  3. Đọc các tiêu đề

    Mỗi dòng Ten: Gia tri. Lấy Content-Length nếu có để biết độ dài thân.
  4. Đọc thân theo Content-Length

    Với POST, đọc đúng số byte mà Content-Lengthnói, không đọc "cho tới khi đóng".

#Phục vụ tệp tĩnh

Đuôi tệpContent-Type
.htmltext/html
.csstext/css
.jsapplication/javascript
.pngimage/png
.jpgimage/jpeg
.jsonapplication/json
khácapplication/octet-stream
Dòng trạng thái và tiêu đề tối thiểu
/* Tra loi thanh cong: */
HTTP/1.1 200 OK\r\n
Content-Type: text/html\r\n
Content-Length: 137\r\n            <- BAT BUOC dung, so byte cua than
Connection: keep-alive\r\n
\r\n
<than tep>

/* Cac ma trang thai phai co: */
MãKhi nàoLý do
200Tệp tồn tại, đọc đượcOK
301Thư mục không có dấu / cuốiChuyển hướng thêm dấu /
400Yêu cầu sai cú phápBad Request
403Tệp tồn tại nhưng không có quyền đọcForbidden
404Tệp không tồn tạiNot Found
405Phương thức không hỗ trợMethod Not Allowed
500Lỗi bên trong serverInternal Server Error

#Chống duyệt đường dẫn

Ghép thẳng
char duong_dan[512]; snprintf(duong_dan, sizeof duong_dan, "%s%s", THU_MUC_GOC, path); FILE *t = fopen(duong_dan, "rb"); /* path = "/../../etc/passwd" -> doc /etc/passwd */
Chuẩn hoá rồi kiểm tra
char ghep[PATH_MAX], thuc[PATH_MAX]; snprintf(ghep, sizeof ghep, "%s%s", THU_MUC_GOC, giai_ma_url(path)); if (!realpath(ghep, thuc)) return tra_404(); if (!nam_trong_goc(thuc)) return tra_403(); FILE *t = fopen(thuc, "rb"); /* Da chuan hoa va kiem tra: khong the thoat ra ngoai goc. */

#Yêu cầu

Tự làm thử

  1. Phân tích yêu cầu HTTP/1.1: phương thức, đường dẫn, phiên bản, tiêu đề, thân.
  2. Phục vụ tệp tĩnh với đúng Content-Type.
  3. Mã trạng thái 200, 301, 400, 403, 404, 405, 500.
  4. Keep-Alive và Content-Length đúng chuẩn.
  5. Liệt kê thư mục khi truy cập một thư mục.
  6. Chống duyệt đường dẫn, bắt buộc.
  7. Ghi log dạng Common Log Format.
  8. Xử lý đồng thời bằng epoll hoặc bể luồng.
  9. Tệp cấu hình: cổng, thư mục gốc, tệp mặc định.
  10. Kiểm thử bằng trình duyệt thật, curl, và ab -n 10000 -c 100.

Trình chấm điểm tự động sẽ được bổ sung ở giai đoạn sau. Hiện tại bạn tự chạy thử trên máy.

Tóm tắt

  • Máy chủ HTTP là dự án đầu tiên kiểm thử bằng trình duyệt thật, curl, và ab.
  • HTTP dùng cả hai cách đánh ranh giới: tiêu đề phân cách bằng dòng, thân bằng Content-Length.
  • Content-Length phải chính xác từng byte, nếu không trình duyệt treo với keep-alive.
  • Chống duyệt đường dẫn là bắt buộc: chuẩn hoá bằng realpath rồi kiểm tra tiền tố, không cấm chuỗi ...
  • Xong khi trình duyệt hiển thị đúng, ab chạy sạch, và /../../etc/passwd trả 403.