基於Netty自己動手實現Web框架

老錢發表於2018-04-24

上節課我們自己動手製作了一個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
複製程式碼

基於Netty自己動手實現Web框架

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
複製程式碼

基於Netty自己動手實現Web框架

堆疊深度

非Java程式設計師總是抱怨Java的框架過於複雜,特別愛拿Java恐怖的呼叫棧說事。比如下面這張圖廣為流傳。

基於Netty自己動手實現Web框架

所以這裡我要亮出httpkids的呼叫棧,我們來看看它到底有多深

基於Netty自己動手實現Web框架

專案程式碼

HttpKids Web Framework based on Netty for Kids of You

基於Netty自己動手實現Web框架

RpcKids RPC Framework based on Netty for Kids of You

基於Netty自己動手實現Web框架

大爆炸

基於Netty自己動手實現Web框架
關注公眾號「碼洞」,讓我們來一起學習一下這個框架的設計與實現。

相關文章