上節課我們自己動手製作了一個RPC框架,本節課我們挑戰一個稍有難度的一點的任務,手動製作一個Web框架。 我不太願意叫什麼MVC框架,因為我在製作這個小專案的時候可沒想過它要怎麼怎麼的MVC,一切皆以易於使用為目標。
首先我們看看這個Web框架使用起來如何簡單
Hello World
import httpkids.server.KidsRequestDispatcher;
import httpkids.server.Router;
import httpkids.server.internal.HttpServer;
public class HelloWorld {
public static void main(String[] args) {
var rd = new KidsRequestDispatcher("/kids", new Router((ctx, req) -> {
ctx.html("Hello, World");
}));
new HttpServer("localhost", 8080, 2, 16, rd).start();
}
}
http://localhost:8080/kids
複製程式碼
KidsRequestDispatcher
是請求派發器,用於將收到的HTTP請求物件扔給響應的RequestHandler
進行處理。
Router
用於構建路由,它負責的是將URL規則和RequestHandler
掛接起來,形成一個複雜的對映表。
Router
為了簡化實現細節,所以沒有支援複雜的URL規則,例如像RESTFUL
這種將引數寫在URL裡面的這種形式是不支援的。
HttpServer
是Web伺服器的核心物件,構建HttpServer除了IP埠之外,還需要提供3個關鍵引數,分別是IO執行緒數、業務執行緒數和請求派發器物件。IO執行緒用於處理套件字讀寫,由Netty內部管理。業務執行緒專門用於處理HTTP請求,由httpkids框架來管理。
一個全面的例子
import java.util.HashMap;
import httpkids.server.KidsRequestDispatcher;
import httpkids.server.Router;
import httpkids.server.internal.HttpServer;
public class HelloWorld {
public static void main(String[] args) {
var router = new Router((ctx, req) -> {
ctx.html("Hello, World"); // 純文字html
})
.handler("/hello.json", (ctx, req) -> {
ctx.json(new String[] { "Hello", "World" }); // JSON API
})
.handler("/hello", (ctx, req) -> {
var res = new HashMap<String, Object>();
res.put("req", req);
ctx.render("playground.ftl", res); // 模版渲染
})
.handler("/world", (ctx, req) -> {
ctx.redirect("/hello"); // 302跳轉
})
.handler("/error", (ctx, req) -> {
ctx.abort(500, "wtf"); // 異常
})
.resource("/pub", "/static") // 靜態資源
.child("/user", () -> { // 路由巢狀
return new Router((ctx, req) -> {
ctx.html("Hello, World");
})
.handler("/hello.json", (ctx, req) -> {
ctx.json(new String[] { "Hello", "World" });
})
.filter((ctx, req, before) -> { // 過濾器、攔截器
if (before) {
System.out.printf("before %s\n", req.path());
} else {
System.out.printf("after %s\n", req.path());
}
return true;
});
});
var rd = new KidsRequestDispatcher("/kids", router); // 請求派發器
rd.templateRoot("/tpl"); // 模版classpath根目錄
rd.exception(500, (ctx, e) -> { // 異常處理
ctx.html("what the fuck it is", 500);
})
.exception((ctx, e) -> { // 通用異常處理
ctx.html("mother fucker!", e.getStatus().code());
});
var server = new HttpServer("localhost", 8080, 2, 16, rd);
server.start();
Runtime.getRuntime().addShutdownHook(new Thread() {
public void run() {
server.stop(); // 優雅停機
}
});
}
}
http://localhost:8080/kids
http://localhost:8080/kids/hello
http://localhost:8080/kids/hello.json
http://localhost:8080/kids/world
http://localhost:8080/kids/error
http://localhost:8080/kids/pub/bootstrap.min.css
http://localhost:8080/kids/user
http://localhost:8080/kids/user/hello
複製程式碼
堆疊深度
非Java程式設計師總是抱怨Java的框架過於複雜,特別愛拿Java恐怖的呼叫棧說事。比如下面這張圖廣為流傳。
所以這裡我要亮出httpkids的呼叫棧,我們來看看它到底有多深
專案程式碼
HttpKids Web Framework based on Netty for Kids of You
RpcKids RPC Framework based on Netty for Kids of You