Web Server 基礎概念
什麼是 Web Server?
💡 比喻:餐廳的櫃台接待 想像一間餐廳:
- 客人(瀏覽器)走進餐廳
- 櫃台接待(Web Server)迎接客人
- 客人說:「我要看菜單」(HTTP Request)
- 接待員把菜單拿給客人(HTTP Response)
- 如果客人點了一道需要現做的菜(動態內容),接待員會把訂單送到廚房(應用程式)
- 如果客人只要一瓶水(靜態檔案),接待員直接從冰箱拿
Web Server 就是這個櫃台接待——負責接收請求、分派工作、回傳結果。
Web Server 的核心工作
客人(瀏覽器) 櫃台接待(Web Server) 廚房(應用程式)
| | |
|--- 我要看首頁 ------->| |
| |--- 這是動態頁面 --------->|
| |<-- 做好了,給你 HTML ------|
|<-- 這是你要的首頁 ----| |
| | |
|--- 我要 logo.png ---->| |
|<-- 直接給你圖片 ------| (靜態檔案不用問廚房) |
Kestrel vs IIS vs Nginx
三大 Web Server 比較
// 三種常見 Web Server 的角色比較
// Kestrel:ASP.NET Core 內建的輕量級 Server
// IIS:Windows 專用的老牌 Server
// Nginx:跨平台的高效能 Server
特性 Kestrel IIS Nginx
─────────────────────────────────────────────────────────────
平台 跨平台 僅 Windows 跨平台 // 部署環境的限制
效能 高 中等 非常高 // 處理請求的速度
內建於 .NET 是 否 否 // 是否開箱即用
反向代理 不建議直接暴露 可以 最常用 // 面對外網的能力
靜態檔案處理 普通 好 非常好 // 處理圖片CSS等
SSL/TLS 支援 支援 最佳 // 加密連線能力
💡 比喻:接待員的等級
- Kestrel 像是餐廳的實習接待員——做事很快,但經驗不足,不適合獨自面對大量客人
- IIS 像是資深接待員——經驗豐富,但只在特定餐廳(Windows)工作
- Nginx 像是五星級飯店的大堂經理——處理大量客人游刃有餘,還能協調多間餐廳
最佳實踐架構
// 生產環境推薦架構
// Nginx 在前面負責接待(反向代理)
// Kestrel 在後面負責處理(應用程式)
外部請求 → Nginx(反向代理)→ Kestrel(ASP.NET Core 應用)
// 這樣的好處:
// 1. Nginx 擅長處理靜態檔案和 SSL
// 2. Kestrel 專心處理業務邏輯
// 3. Nginx 提供額外的安全防護
靜態檔案 vs 動態內容
靜態檔案
// Program.cs 中啟用靜態檔案服務
var builder = WebApplication.CreateBuilder(args); // 建立應用程式建構器
var app = builder.Build(); // 建構應用程式
app.UseStaticFiles(); // 啟用 wwwroot 資料夾中的靜態檔案服務
// 靜態檔案的存放位置:
// wwwroot/
// ├── css/ // 樣式表檔案
// │ └── site.css // 網站主要樣式
// ├── js/ // JavaScript 檔案
// │ └── app.js // 前端邏輯
// ├── images/ // 圖片檔案
// │ └── logo.png // 網站 Logo
// └── favicon.ico // 網站圖示
動態內容
// 動態內容由 Controller 或 Minimal API 產生
// 每次請求都可能回傳不同的結果
app.MapGet("/api/time", () => // 定義一個 API 端點
{
return DateTime.Now.ToString(); // 每次呼叫回傳當下時間(動態)
});
app.MapGet("/api/users/{id}", (int id) => // 根據使用者 ID 查詢
{
return $"使用者 {id} 的資料"; // 根據參數回傳不同結果
});
Port、Binding、Listen
什麼是 Port?
💡 比喻:大樓的房間號碼 IP 位址像是一棟大樓的地址,Port 就是大樓裡的房間號碼。
- 80 號房 → HTTP 接待室
- 443 號房 → HTTPS 加密接待室
- 5000 號房 → 你的 ASP.NET Core 應用
- 一棟大樓(一台電腦)可以有 65535 個房間
設定 Port 和 Binding
// Program.cs 中設定監聽的 Port
var builder = WebApplication.CreateBuilder(args); // 建立建構器
// 方法一:透過程式碼設定
builder.WebHost.UseUrls(
"http://localhost:5000", // 監聽 HTTP 5000 Port
"https://localhost:5001" // 監聽 HTTPS 5001 Port
);
// 方法二:透過命令列參數
// dotnet run --urls "http://localhost:8080" // 指定單一 Port
// dotnet run --urls "http://*:80" // 監聽所有網路介面的 80 Port
// launchSettings.json 中的設定
{
"profiles": { // 啟動設定檔
"MyApp": { // 應用程式名稱
"commandName": "Project", // 使用專案啟動
"applicationUrl": "https://localhost:5001;http://localhost:5000", // 監聽的網址
"environmentVariables": { // 環境變數
"ASPNETCORE_ENVIRONMENT": "Development" // 開發環境
}
}
}
}
HTTP Request/Response 完整流程
一個請求的完整旅程
// 當你在瀏覽器輸入 https://myapp.com/products 時發生了什麼?
步驟 1:DNS 解析 // 把網址轉成 IP 位址
myapp.com → 123.45.67.89 // 就像查電話簿
步驟 2:TCP 連線 // 建立通訊通道
三次握手 (SYN → SYN-ACK → ACK) // 就像打電話先確認對方在
步驟 3:TLS 握手(HTTPS) // 建立加密通道
交換憑證、協商加密方式 // 就像雙方約定暗號
步驟 4:發送 HTTP Request // 送出請求
GET /products HTTP/1.1 // 我要看商品列表
Host: myapp.com // 目標主機
Accept: text/html // 我想要 HTML 格式
步驟 5:Server 處理請求 // Web Server 接收並處理
Nginx → Kestrel → Controller → DB // 經過層層處理
步驟 6:回傳 HTTP Response // 送回結果
HTTP/1.1 200 OK // 狀態碼 200 表示成功
Content-Type: text/html // 內容是 HTML
<html>...</html> // 實際的網頁內容
步驟 7:瀏覽器渲染 // 把 HTML 變成你看到的畫面
常見 HTTP 狀態碼
// 狀態碼就像櫃台接待的回應
200 OK // 沒問題,這是你要的東西
301 Moved // 搬家了,去新地址找
304 Not Modified // 跟上次一樣,用你的快取就好
400 Bad Request // 你的要求我看不懂
401 Unauthorized // 你還沒登入,請先登入
403 Forbidden // 你沒有權限看這個
404 Not Found // 找不到你要的東西
500 Server Error // 我們這邊出問題了,不是你的錯
502 Bad Gateway // 後面的廚房(應用程式)沒回應
503 Unavailable // 我們暫時休息中
appsettings.json 環境設定
// appsettings.json - 基本設定(所有環境共用)
{
"Logging": { // 日誌設定區塊
"LogLevel": { // 日誌等級設定
"Default": "Information", // 預設記錄 Information 以上
"Microsoft.AspNetCore": "Warning" // ASP.NET Core 只記錄 Warning 以上
}
},
"AllowedHosts": "*", // 允許所有主機名稱存取
"ConnectionStrings": { // 資料庫連線字串
"DefaultConnection": "Server=localhost;Database=MyDb;Trusted_Connection=true" // 本機 SQL Server
},
"AppSettings": { // 自訂應用程式設定
"SiteName": "我的網站", // 網站名稱
"MaxUploadSize": 10485760 // 最大上傳大小(10MB)
}
}
// appsettings.Development.json - 開發環境專用(會覆蓋基本設定)
{
"Logging": { // 開發環境的日誌設定
"LogLevel": { // 日誌等級
"Default": "Debug" // 開發時記錄更詳細的 Debug 等級
}
},
"AppSettings": { // 開發環境的應用設定
"SiteName": "我的網站(開發版)" // 開發版網站名稱
}
}
// 在程式中讀取設定
var builder = WebApplication.CreateBuilder(args); // 建構器會自動載入設定檔
// 讀取設定值
var siteName = builder.Configuration["AppSettings:SiteName"]; // 用冒號分隔階層
var maxSize = builder.Configuration.GetValue<int>("AppSettings:MaxUploadSize"); // 轉型讀取
// 用強型別讀取設定(推薦做法)
builder.Services.Configure<AppSettings>( // 綁定設定到類別
builder.Configuration.GetSection("AppSettings") // 指定設定區塊
);
// 在 Controller 或 Service 中注入使用
public class HomeController : Controller // 控制器
{
private readonly AppSettings _settings; // 設定欄位
public HomeController(IOptions<AppSettings> options) // 透過 DI 注入
{
_settings = options.Value; // 取得設定值
}
}
🤔 我這樣寫為什麼會錯?
❌ 錯誤一:在生產環境直接暴露 Kestrel
// 錯誤:讓 Kestrel 直接面對外部網路
builder.WebHost.UseUrls("http://*:80"); // 直接監聽 80 Port 對外服務
// 問題:Kestrel 缺乏完整的安全防護
// 沒有速率限制、沒有請求過濾、沒有 DDoS 防護
// ✅ 正確做法:前面加上 Nginx 反向代理
// Nginx 負責對外,Kestrel 只監聽 localhost
builder.WebHost.UseUrls("http://localhost:5000"); // 只監聽本機,由 Nginx 轉發
❌ 錯誤二:把敏感資訊寫在 appsettings.json
// 錯誤:把密碼和金鑰直接寫在設定檔中
{
"ConnectionStrings": {
"Default": "Server=db;Password=MyP@ssw0rd123;" // 密碼明文寫在設定檔!
},
"ApiKeys": {
"Stripe": "sk_live_abc123" // API 金鑰直接暴露!
}
}
// 這些設定檔可能被提交到 Git,所有人都看得到
// ✅ 正確做法:使用環境變數或 User Secrets
// 開發環境用 dotnet user-secrets set "ApiKeys:Stripe" "sk_live_abc123"
// 生產環境用環境變數 export ConnectionStrings__Default="Server=db;..."
❌ 錯誤三:不理解環境設定的覆蓋順序
// 錯誤:以為 appsettings.json 的值不會被覆蓋
// 實際的載入順序(後面的會覆蓋前面的):
// 1. appsettings.json // 基礎設定(最先載入)
// 2. appsettings.{Environment}.json // 環境設定(覆蓋基礎)
// 3. User Secrets(開發環境) // 開發密鑰(覆蓋環境)
// 4. 環境變數 // 系統變數(覆蓋密鑰)
// 5. 命令列參數 // 最後載入(最高優先)
// ✅ 正確理解:後載入的設定會覆蓋先載入的
// 所以生產環境的環境變數會覆蓋 appsettings.json 中的同名設定