核心技术靠化缘是要不来的——自己动手写 web 框架

2018-04-27 09:46:10 +08:00
 codehole

上节课我们自己动手制作了一个 RPC 框架,本节课我们挑战一个稍有难度的一点的任务,手动制作一个 Web 框架。考虑到 Java 的 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 根目录
		.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

大爆炸

关注公众号「码洞」,让我们来一起研究一下这个框架的设计与实现。

4165 次点击
所在节点    程序员
26 条回复
deadEgg
2018-04-27 14:29:47 +08:00
base on netty

我记得 netty 已经有人造过类似的轮子了吧。
deadEgg
2018-04-27 14:30:35 +08:00
@Rhonin 送你个 block
xiangyuecn
2018-04-27 14:31:30 +08:00
IIS、Tomcat 上来就是干,还是自己去撸有快感

打个广告先:
https://www.jianshu.com/p/a06f9c74a439 nodejs 写的“简易 web 框架”,所有代码 100 行不到,支持 post、get 请求、任性复杂路由
wanderlustLee
2018-04-27 14:32:17 +08:00
题主只是想靠自己的知识这个框架练手 为什么有这么多人来酸 题主支持你
codehole
2018-04-27 14:37:54 +08:00
jin5354
2018-04-27 14:45:30 +08:00
v 友真的很严格

这是一个专为移动设备优化的页面(即为了让你能够在 Google 搜索结果里秒开这个页面),如果你希望参与 V2EX 社区的讨论,你可以继续到 V2EX 上打开本讨论主题的完整版本。

https://www.v2ex.com/t/450278

V2EX 是创意工作者们的社区,是一个分享自己正在做的有趣事物、交流想法,可以遇见新朋友甚至新机会的地方。

V2EX is a community of developers, designers and creative people.

© 2021 V2EX