V2EX = way to explore
V2EX 是一个关于分享和探索的地方
现在注册
已注册用户请  登录
codehole
V2EX  ›  程序员

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

  •  
  •   codehole ·
    pyloque · 2018-04-27 09:46:10 +08:00 · 4302 次点击
    这是一个创建于 2405 天前的主题,其中的信息可能已经有所发展或是发生改变。

    上节课我们自己动手制作了一个 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

    大爆炸

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

    26 条回复    2018-04-27 14:45:30 +08:00
    Shynoob
        1
    Shynoob  
       2018-04-27 11:04:15 +08:00
    能提到 github 上吗
    nine99
        2
    nine99  
       2018-04-27 11:50:31 +08:00
    看上去很牛啊
    codehole
        3
    codehole  
    OP
       2018-04-27 11:55:34 +08:00
    codehole
        4
    codehole  
    OP
       2018-04-27 11:56:39 +08:00
    @nine99 其实就写了几天,只是用来学习的,以后会考虑进入生产检验,只是没那么快
    awesomes
        5
    awesomes  
       2018-04-27 12:02:42 +08:00
    建议最好从机器码开始
    torbrowserbridge
        6
    torbrowserbridge  
       2018-04-27 12:03:45 +08:00
    这个广告不行。完全没有关注的兴趣。
    codehole
        7
    codehole  
    OP
       2018-04-27 12:04:20 +08:00
    @awesomes 哪里哪里,应该要自己先做一个 CPU、内存、操作系统、编译器等等等等
    hsuan
        8
    hsuan  
       2018-04-27 12:04:46 +08:00 via Android
    轮子谁都能造,关键是造的轮子能不能上路。
    codehole
        9
    codehole  
    OP
       2018-04-27 12:06:21 +08:00
    @torbrowserbridge 广告的目的只是为了让好东西让更多的人看到
    codehole
        10
    codehole  
    OP
       2018-04-27 12:06:56 +08:00
    @hsuan 轮子不是谁都能造的,能够上路的轮子也不是一天造起来的
    niubee1
        11
    niubee1  
       2018-04-27 12:19:35 +08:00   ❤️ 1
    大凶带, 如果不是 Java 新版本加入了这些新特性, 你敢保证你不需要设计模式加持么?
    codehole
        12
    codehole  
    OP
       2018-04-27 12:24:59 +08:00
    @niubee1 只用了 java8 的 lambda,其它的都不是新特性
    q397064399
        13
    q397064399  
       2018-04-27 13:16:19 +08:00
    没有意义,轮子的 feature 不够,没有事务 没有 Redis 没有 ORM 没有 OAUTH2.0 没有事件 没有 AOP 一堆东西都没有,
    要把这些东西集成到你这个框架,太难。
    调用栈太多,麻烦的地方是排错,绝大部分程序员希望是有一个大而全的,且能够开箱即用的东西,
    没人希望去造轮子。
    codehole
        14
    codehole  
    OP
       2018-04-27 13:39:59 +08:00
    @q397064399 Go 的 Gin 和 Echo 框架都没有你说的这些东西,但是业界使用多着呢,node 的 express 框架也没有你说的这些东西,使用也是很广泛,python 的 tornado 虽然有 orm,但是也几乎无人使用这个 orm,照样阻挡不了它的流行。
    另外 AOP 这个东西真不是必须有的。
    codehole
        15
    codehole  
    OP
       2018-04-27 13:51:29 +08:00
    @q397064399 作为一个纯粹的 web 框架,httpkids 就是开箱即用的。
    q397064399
        16
    q397064399  
       2018-04-27 14:05:33 +08:00
    @codehole #14 等你业务大了,,需要权限 事务 审计的时候就用得到了 AOP,不过现在流行微服务, 权限 审计 都放到业务网关等地方做掉了,服务本身只需要关注业务逻辑了, 另外没有 ORM 静态语言组装对象太难受,哪怕是 mybatis 这种需要手写 sql 的框架,也省了不少的映射时间,
    Rhonin
        17
    Rhonin  
       2018-04-27 14:09:05 +08:00
    这里不是你的个人博客,也不是教学网站,不要在这里"讲课"
    misaka19000
        18
    misaka19000  
       2018-04-27 14:12:18 +08:00
    你这个设计理念是不是参考了 blade
    codehole
        19
    codehole  
    OP
       2018-04-27 14:13:11 +08:00
    @misaka19000 准确的说是 spark-java
    codehole
        20
    codehole  
    OP
       2018-04-27 14:14:31 +08:00
    @q397064399 ORM 你可以使用市面上的很多开源框架,简单组装一下就行了。不过再过几天,我会放出自己撸的一个 ORM 框架
    deadEgg
        21
    deadEgg  
       2018-04-27 14:29:47 +08:00
    base on netty

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

    打个广告先:
    https://www.jianshu.com/p/a06f9c74a439 nodejs 写的“简易 web 框架”,所有代码 100 行不到,支持 post、get 请求、任性复杂路由
    wanderlustLee
        24
    wanderlustLee  
       2018-04-27 14:32:17 +08:00 via iPhone   ❤️ 2
    题主只是想靠自己的知识这个框架练手 为什么有这么多人来酸 题主支持你
    codehole
        25
    codehole  
    OP
       2018-04-27 14:37:54 +08:00
    jin5354
        26
    jin5354  
       2018-04-27 14:45:30 +08:00
    v 友真的很严格
    关于   ·   帮助文档   ·   博客   ·   API   ·   FAQ   ·   实用小工具   ·   3412 人在线   最高记录 6679   ·     Select Language
    创意工作者们的社区
    World is powered by solitude
    VERSION: 3.9.8.5 · 28ms · UTC 11:36 · PVG 19:36 · LAX 03:36 · JFK 06:36
    Developed with CodeLauncher
    ♥ Do have faith in what you're doing.