专栏首页Web行业观察手写一个http容器【上】决策树与路由表

手写一个http容器【上】决策树与路由表

Linux之父说过,伪代码是最好的语言,因为它能够表达所有的逻辑。所以本文所有的代码示例都是伪代码。


  • ALFP协议

如果让我来定义http协议的话,我会给他取一个完全不同的名字:ALFP(Application Layer Fetch Protocol,应用层请求协议)。2020年我甚至忘了“HTTP”的全称是什么?好像是“超文本传输协议”?然后意识到这种古老的,对新人不友好的首字母缩写还是不要拆开来读比较好,况且“超文本”这个词已经鲜为人知了,但至少“超文本”是存在于应用层的东西,再加上“fetch”这个单词能够非常形象的概括出http协议的特点:“抓取”意味着有请求有回应。所以我认为HTTP协议如果改名叫ALPF协议会更有爱,更名的灵感来自ALPN协议(应用层协议协商),如果更名成功,ALFP协议能让00后快速地了解这个协议的功能,减少他们的学习成本,同时还能满足我们老玩家的沙雕强迫症。

------------------------正经的分割线------------------------


  • 手写一个http路由器

一本正经的胡说八道结束了,下面聊聊正题。当我们用烂了Express,Koa等http后端框架就忍不住自己手写一个,如果你懒的去学习一个新的框架,仅仅是为了实现一个小的web app,手写后端框架往往是最好的选择。我将用nodejs制作一个最简单的http后端路由模型,让大家明白web框架一点也不复杂,人人都可以手写。

由于是精简版的后端框架,不用考虑什么负载均衡和容灾,在一台虚拟机服务器上只要考虑ALFP的核心理念就行,关键词就是“应用层”和“抓取”,整个http协议不过如此,我们只要考虑当一个请求进来以后我们先要做什么,再做什么,最后做完什么以后响应回去就完事。中间的每一步就是相互独立的“中间件”。

但是为了写一个通用的后端框架,还是要考察一下大多数网络app通常都有哪些架构,再将这些常见的需求组合起来创造我们自己的web框架。关于需求,Koa框架提出的“洋葱图”给了我们一些参考:

别的不用关心,洋葱图上有一些比较重要的后端功能是我们参考的对象,比如路由,会话,缓存和异常处理。绝大多数web app的架构拆开来看也就这些东西。

由于21世纪20年代以后前端架构基本统一了,浏览器端,桌面端,移动端已经没什么区别了,统统都叫app,只不过需要服务器支持的叫做网络app,不需要的就叫app,甚至所有的主流app都由JS,wasm,H5和Css这4个基本语言写成。

以上是功能,再从性能上考虑,为了不阻塞主线程,所有的中间件都要运行在事件循环引擎之上,换言之每个中间件都是一个promise。


  • 决策树与路由表

中间件之间不仅是串行的,而且是树形的:上一个中间件的计算结果有可能决定下一个中间件,所以整个中间件网络是一棵决策树,在决策树上迭代的过程就叫“路由”,路由的寻路依据就是我们的“路由表”。

路由表有多种形式,不同的业务逻辑可以设计不同的路由表,这里推荐一种常用的,根据Restful动词来构建路由表的策略。Restful动词也就是对数据所有可能操作组成的分类网,我们熟悉的“增删改查”指的就是这些动词,下图展示了这些动词的一部分:

根据数据操作动词来构建决策树不失为一个很好的选择,动词既可以写在http method头部字段里,也可以写在url路径上。至于决策树在代码上怎样体现,可根据剧情选择if/else树或者嵌套的散列表,通常散列表可以让每个决策花费的时间相等,比较适合决策树较大的情况。


  • URL路径的优雅处理

说到路径,后端框架一般都把url上所有路径存放到一个列表中,但由于url路径之间是正斜杠分隔的,为了和空格分隔符统一,多个连续的正斜杠可以看成一个,列表只存放有意义的路径名,所以/path/to和/path//to和/path/to/表达了相同的含义,对应的路由表都是['path', 'to']。生成路由列表的表达式如下:

// 生成路由表的伪代码
request.paths = request.urlPath.split("/").filter(p => p.trim());

request.paths就是路由表,保存着url路径上从左到右每一个路径,每当经过一层路由就让paths.shift()一下,然后根据request.paths[0]来选择下一个中间件。


  • 入口设计(index.html)

入口设计很简单,即当url路径为空的时候需要返回index.html这个静态文件,这时候request.paths是一个空数组。

// 网站入口的伪代码

if (request.paths.length === 0) {
  await new Promise((resolve, reject) => {
    response.setHeader("Content-Type", "text/html");

    const r = require("fs").createReadStream("path/to/index.html");
    r.on("error", err => reject(err));
    r.on("end", resolve);
    r.pipe(response);
  });
}

  • 会话层与认证

很多地方都要考虑入口的特殊性,除了index.html,还要考虑每个request进来后要做的第一件事是什么?第一件事通常是要对这个request进行认证,对不对?无论是通过用户名密码认证还是根据会话凭证来认证,这都是必须要做的(即使它请求的是只读的资源)。

根据剧情需要,可以将会话凭证之外的会话信息存放在客户端或者服务端。反正流行的凭证格式JWT是建议将其他信息存在客户端,比如一些用户的个人信息,反正加密后的数据寄存在前端没有危害,但是数据量大的情况下可以考虑存在后端。下面是一个将token存在前端的例子:

// 会话层token认证的伪代码

module.exports = async function() {
  const req = this.request;
  const res = this.response;
  // myToken是假想的一种凭证插件,类似JWT
  const myToken = require("path/to/myToken");

  req.session = "";
  //   authorization头部用来存放加密的token
  const token = req.headers["authorization"];
  if (!token) return;

  //   secretKey是一个密钥,用于加解密token
  req.session = await myToken.verifyWithKey(token, secretKey).catch(err => {
    if (err.name === "TokenExpiredError") {
      // my-token-expire这个自定义头部提示前端应该删除这个token了
      res.setHeader("my-token-expire", 1);
    } else if (err.name === "invalidTokenError")
      throw "凭证损坏:" + err.message;
  });
};

request进来的时候,我们将它携带的token中解密出来的数据存放到request自己身上,供之后的中间件使用,同时还要做好错误处理。


(未完待续。。。)

本文分享自微信公众号 - WebHub(myWebHub),作者:金恒昱

原文出处及转载信息见文内详细说明,如有侵权,请联系 yunjia_community@tencent.com 删除。

原始发表时间:2020-01-31

本文参与腾讯云自媒体分享计划,欢迎正在阅读的你也加入,一起分享。

我来说两句

0 条评论
登录 后参与评论

相关文章

  • 手写一个http容器【下】下一代渐进式web框架

    web后端最常用的功能之一是静态文件的托管,也就是那些存放在服务器上的只读文件,可以让前端自由下载。最直接的实现手段就是将url的路径和文件系统的路径一一对应,...

    Jean
  • 《大型网站技术架构》读书笔记四:瞬时响应之网站的高性能架构

    此篇已收录至《大型网站技术架构》读书笔记系列目录贴,点击访问该目录可获取更多内容。

    Edison Zhou
  • 【周志华深度森林第二弹】首个基于森林的自编码器,性能优于DNN

    【新智元导读】或许你还记得南大LAMDA教授周志华和学生冯霁在今年早些时候发表的“深度森林”论文,他们认为基于决策树集成的方法同样可以构建深度学习模型,并提出深...

    新智元
  • URL 从输入到页面渲染全流程

      通过DNS将域名解析成IP地址。在解析过程中,按照浏览器缓存、系统缓存、路由器缓存、ISP(运营商)DNS缓存、根域名服务器、顶级域名服务器、主域名服务器的...

    用户7657330
  • 【面试题】2018年最全Java面试通关秘籍汇总集!

    前几天在交流群里有些小伙伴问面试相关的试题,当时给出了一些问题,苦于打字太累就没写下去了,但觉得这是一个很不负责任的表现,于是下来整理了一下近几年的私藏,特分享...

    Java后端技术
  • Java面试 32个核心必考点完全解析

    1~3年内从工程师到高级工程师发展,夯实基础,重点提高工作基础能力,培养技术的深度和广度,对不同方向的新技术保持强烈的好奇心和学习心

    本人秃顶程序员
  • UCL等三强联手提出完全可微自适应神经树:神经网络与决策树完美结合

    【新智元导读】UCL、帝国理工和微软的研究人员合作,将神经网络与决策树结合在一起,提出了一种新的自适应神经树模型ANT,打破往局限,可以基于BP算法做训练,在M...

    新智元
  • 今天中午刚收到的,快手Java开发(书面offer) 烫手!!!

    今天中午刚收到书面offer,距离面试完过去了24天...虽然审批流程如此漫长,但是不得不说快手的面试体验很棒和面试流程效率很高

    Java架构技术
  • 211渣硕,海投200+Java岗,面50家侥幸过了7家!!!

      211渣硕,电气专业转JAVA后台,看书很少多数是在刷面经,能找到工作实属侥幸,现献上自己的面经,希望能帮助到别人

    秃顶的Java程序员
  • 硬核干货!2020最新Java最常见面试题汇总。Guide哥倾心整理!

    大家好,我是Guide哥!这篇文章最开始是在我的知识星球分享过,分享之后里面的题目引起了大家的共鸣,所以拿出来分享一下,希望对大家有帮助!觉得不错的话,一定要看...

    Guide哥
  • 助力秋招-独孤九剑破剑式 | 10家企业面试真题

    场景描述:这篇文章是来自一个《大数据技术与架构》的一个读者-逆流而上Mr李,作者总结了自己在应聘过程中的面试题以及经验,珍贵的什么程度?也许你下次的问题就在这上...

    王知无-import_bigdata
  • 关于浏览器方向的大厂面试题

    所有的性能优化中,缓存是最重要也是最直接有效的,毕竟现在都这么忙,可等不了网页转菊花。

    前端迷
  • Java 面试题全记录 多处搜集 灵魂拷问 持续更新

    大佬总结的面试题纲: https://github.com/farmerjohngit/myblog/issues/21

    heasy3
  • 我花了一周,总结了一份Java学习/面试自测指南!200+道Java最常见面试题!

    另外,准备面试的小伙伴,一定要根据自身情况制定好复习计划! 并且,你最好还要时不时自测一下,对着一些面试常见的问题进行自问。

    Guide哥
  • 2018秋招面经-后端开发

    牛客网
  • 2018秋招面经-后端开发

    牛客网
  • 2018秋招面经-后端开发

    牛客网
  • 前端性能优化不完全手册

    将请求优先分配给压力较小的服务器,它可以平衡每个队列的长度,并避免向压力大的服务器添加更多的请求。

    Peter谭金杰
  • 后端架构师技术图谱

    转自: GitHub/architect-awesome , 大体结构如下(更新时间: 2018-06-22)

    用户1216491

扫码关注云+社区

领取腾讯云代金券