React + Typescript 组件的工程化治理实践

最近参与了一个 React + Typescript 组件项目,这个项目后期会开源,对代码的质量和工程化上有比较高的要求,因此需要进行工程化治理。通过这次工程化治理,笔者算是梳理清楚了一个 React + Typescript 第三方组件所需要的一些工程化方面的基础设施,在这里总结并分享给大家。 这次的工程化治理主要分以下几个方面: 静态检查:TypeScript + ESLint 开发体验:打包工具和 Mono-repo 管理 代码质量:测试 静态检查 TS 和 ESLint 这些工具本质上是对代码做静态检查,尽早发现隐藏的 bug。在 TS 出现之后,TS 有 ESLint 没有的类型检查,并且也具备 ESLint 具有的语法错误检查的能力,所以目前我们用 ESLint 主要是利用社区中数量庞大的 Lint 规则来对代码风格做一个规范,利用工具的方式去推行一些最佳实践。TS 则主要负责对代码语法和语义上的错误进行静态检查。另外,TS 本身是一个全新的语言,使用 TS 可以享受到一些 JS 没有的语言特性。 从 AnyScript 到 TypeScript 用 TS,一个很重要的区别就是有没有在配置中打开 strict 选项。如果没有的话,那其实你用的就是 AnyScript,在类型上基本没有约束,和 JS 没有太大的区别。如果是从 JS 迁移到 TS 的项目,这个选项应该关闭,因为老的 JS 代码没有写类型。但如果是全新的纯 TS 项目,strict 是一定要打开的。现在 CRA 这样的脚手架创建的项目也是默认开启了 strict 模式的。 打开 strict 模式其实很简单,难的是如何在 strict 模式下优雅的写 TS 代码。下面说说一些 strict 模式下的常见问题以及一些类型的技巧:...

November 15, 2019

五周年,一点碎碎念

五年,感谢团队的中流砥柱 今天是 2019 年 9 月末,离 2014 年开学的时候,已经过去了整整 5 年了。2014 级同学是真正意义上的第一届木犀同学,从大一开始就进入团队,见证了整个团队从无到有的过程。这篇博客不是什么官方的五周年回顾,主要还是我作为团队中的一份子,在这个时间点,想写一写自己的想法,记录一下。再过几年来看,也许会给未来的人提供一些动力。 离我上一次写纪念博客的 2016 年 12 月,已经过去了三年,这三年里面团队又迎来了很多的新同学,2016 年入学的木犀同学现在已经大四,都有了很好的出路,前途光明。三年之间,主管和组长按一年左右的周期进行更替,目前来看,运行的挺好。全体大会,组会,夏令营,冬令营,秋季招新,春季招新,秋游,聚餐,更进度,项目会,i华大值班,这些团队的日常,也都按部就班的进行着。 这些日常的事情很小,很琐碎,周而复始,年年如此。但还是需要有细心,有责任感的同学,才能很好的完成这些看起来不起眼的小事。坚持是很难的,我一直觉得能做到每天更进度的人,一定不会差到哪里去。坚持在团队承担这些责任,也是很难的。在木犀做组长或者主管,就像是要维护一个大家庭。和其他的普通社团不一样,在木犀,事情做的好不好,很难有量化的标准,把事情做好了,也并不会有多么光芒耀眼的荣誉。说白了,在团队坚持下去,全靠自己打鸡血。这些全靠自己内心的热情,对团队的未来的信心,对每一位团队同学的关心,这是人的最大的善意。 所以团队可以顺利的走到现在,靠的是主管和组长同学的坚持和付出。掌声应该送给这些默默付出的同学。木犀有五个组,在校有几十个人,不可能靠着一两个人就可以正常运转。每个组其实都有人才上的高峰和低谷,但不管是在什么时期,我们都没有放弃,我很感谢低谷时期还陪伴团队一起度过的主管和组长们,当然还有初创时期的组长们,没有你们就不会有这个团队。 还有一部分同学,作为团队的项目主力,一手打造了匣子这个产品,之前团队人少,这部分人和管理同学很大一部分是重合的。还有负责 i华大事务的同学,没有 i华大方面的支持,我们在资金和场地上都没法维持。掌声同样送给他们。 下一个五年,我的思考 如果说这一个五年是团队起步的阶段,下一个五年,我希望是团队规范化发展的时候。 建立完善的知识库 招新也好,培养计划也好,我希望我们都可以沉淀为文档。这些事情里面都有一些坑,没有必要每一届人都踩一遍。比如招新,有前一届的作为参考,后面可以快速的上手,留出时间来做一些创新的事情(比如拍宣传视频)。 培养计划呢,则需要有经验的人来审核,保证质量。每一届都定一个新的培养计划是一件非常不值得的事情。重复的低水平建设,比不上持续的高水平迭代。101 是一个很好的尝试,希望在下一个五年,101 可以成为一个完善的互联网学习知识库。 甚至组长和主管的工作职责,也可以有一个参考手册,来沉淀一些我们的经验之谈。 做多元化的产品尝试 在做好匣子为首的校园产品,以及工作台为主的内部效率工具之外。我们需要探索不同的产品方向。在关注校园内的需求的同时,可以做一些跳出校园范围的产品。 基于加强团队生存能力的考虑下,我们也会做商业化产品的尝试。希望在下一个五年,我们可以有一些可以盈利的小产品,为我们带来一些收入,让我们有独立发展的底气。 打造完整的技术基础设施 我们要打造云平台/数据平台/日志/埋点/监控/微服务框架/运维/Serverless 等等基础设施。搭好了基础设施,产品才能顺利的运行,数据和报表才能被轻松的获得。基础平台和产品业务开发是我们技术的两个大方向,从某种意义上来说,我们和字节跳动很像,字节跳动被称为 App 工厂,有着完善的技术中台机制。而我们也有着一个孵化众多产品的目标,因此我们同样需要完善的技术基础设施,来加速开发和速度和提升开发者的体验。 技术基础设施同样也是很有趣的一个技术方向,现在的大公司都是有相关的职位的。这让我们团队的技术同学有着更广阔的发展空间,可以根据自己的兴趣来选择自己的方向。 整合校友资源 团队五年之后,随着前几届同学的毕业,终于有了自己的校友资源。校友资源的作用是: 在大四实习完回学校时,可以参与团队的基础设施建设,或参与团队知识库的沉淀,也可以给团队的低年级同学做分享,给大三同学做就业指导。也可以用自己为例子,在学院/学校层面宣传团队。 设计组同学毕业后可以参与设计稿的点评,提出有价值的修改意见。 参加工作/实习的同学,可以组织定期的线上/线下的聚会。线上的聊天可以包装为木犀电台这一个栏目,主要是聊自己公司的情况,分享工作中的困惑,互通有无,目的是让大家都工作的更开心。在这个行业中相互扶持,相互交流。 在资金上赞助团队(小数额,一次性)。 如果是比较成功的校友,还可以和团队进行更大力度的合作。 其他方面的话,比如团队文化的建设,团队日常的工作,我也有很多的想法。不过这些主要是在校的主管和组长在做的事情。我这边没有太多的发言权。这些事情就应该交给主管和组长去做。所以这里就不写这些了。 以上的几点思考,也是我个人的思考。分享出来,以供讨论。 结语 不知道几年之后,团队会是怎样的?乐观的来说,肯定是越来越好的。这些年团队一路过来,个中滋味,恐怕只有团队的每个人自己知道。这里还是想说,但行好事,莫问前程。我相信我们做的事情是有价值的。过程比结果更重要。在团队,要享受这边的人和事,就算一切都归零,在团队的这段记忆,会是大家很难忘却的青春。

November 15, 2019

k3s 安装小记

k3s 刚出来的时候,我刚好看到这个项目,然后了解到这是一个轻量级的 k8s 发行版。之前刚好遇到在阿里云学生机(1C2G)上安装 k8s 后内存占用太多的问题,因此就决定尝试。最后的效果超出了预期,k3s 可以帮助我们在低配置机器上运行 k8s 集群,缓解了 k8s 对于资源占用的压力,降低了服务器的成本。 k3s 简介 k3s 是 Rancher 推出的轻量级 k8s。k3s 本身包含了 k8s 的源码,所以本质上和 k8s 没有区别。但为了降低资源占用,k3s 和 k8s 还是有一些区别的,主要是: 使用了相比 Docker 更轻量的 containerd 作为容器运行时(Docker 并不是唯一的容器选择) 去掉了 k8s 的 Legacy, alpha, non-default features 用 sqlite3 作为默认的存储,而不是 etcd 其他的一些优化,最终 k3s 只是一个 binary 文件,非常易于部署 所以 k3s 适用于边缘计算,IoT 等资源紧张的场景。同时 k3s 也是非常容易部署的,官网上提供了一键部署的脚本。 安装环境 本文的安装环境: 阿里云 1C2G 机器若干,运行 CentOS 7.6 64位 k3s v0.5.0 安装脚本 https://get.k3s.io 这是 k3s 的安装脚本。我们直接运行这个脚本就可以安装 k3s。因为我们需要在 k3s 运行之前做一些事情,所以运行脚本的时候我们选择只安装,不启动 k3s...

June 4, 2019

木犀的第二代后端架构

2019 是木犀的第五年。前五年,木犀的后端发展经过了一个从无到有的过程,从最初的单机 Flask + 自己部署的数据库到如今基于 K8s 的分布式架构 + 云数据库,从 Python 到 Golang,我们逐渐确立了我们的第二代架构。 如今,已经没有必要去细究这个过程究竟是如何逐步发生的。现在要做的,就是确立我们现在的新架构,并且在今年把我们的主力应用都切换到新架构上,为下一个五年的发展打好基础。 新架构,简单的说,就是 Cloud Native,拥抱了当前非常流行的容器云,Golang 等云原生相关的技术。其实这套架构没什么特殊的,用到的都是热门技术,但这依然是我们经过了过去几年间不断的摸索而总结出来的。后面我们会在这套技术上深入挖掘,争取知其然,知其所以然。 首先一张图总结: 应用技术栈 动态语言一时爽 我们需要一门强类型,静态编译型,高性能,高并发,稳定,健壮的语言,来作为我们的主力开发语言。Golang 就是一样的一门语言,而且我们重点关注的云计算领域,Golang 是统治级的存在。所以我们选了 Golang 作为今后我们应用开发的主力语言。 Java 也是备选之一,今后可以调研 Java 的运行时开销,来确定是不是使用 Go + Java 作为我们的技术栈。目前来看 Golang 做 Web 开发还是比较快的,Java 的开发速度也是一个不确定因素。Java 的好处就是就业市场上需求广,生态繁荣,Web 开发上社区有多年的积累,大数据领域 Java 是绝对的主流。 在服务内部通信上我们使用 grpc。grpc 对 Golang 的技术栈比较友好,以后也有跨语言通信的能力。服务发现和服务治理之类,K8s 其实已经做好了,所以我们不用做特别的工作。 在开发流程规范上,我们针对 Go Web 项目有一个规范。 DB 主力是阿里云 RDS 上的 MySQL,单独的云端数据库让我们免除了运维的麻烦,但 SQL 查询性能,建索引等等还是需要我们自己把关。 另外自己搭了一个 Mongo,因为阿里云上的 Mongo 太贵了。Mongo 要做好自动数据备份工作。 容器调度 Kubernetes 在木犀已经投入生产接近两年了,我们在 Kubernetes 上有了一定的使用过经验,踩了一些坑。对于原理我们还需要深入研究。 对 K8s 抱有疑问的同学可以看这篇文章 Kubernetes 是下一代操作系统 | 面向 Kubernetes 编程...

March 27, 2019

后端工作方向介绍

这篇博客是写给团队后端方向的同学看的。主要是介绍我在工作一年之后对于后端工作方向的一些新的认知(虽然职位是前端,但在这个大环境下面,对后端的工作内容还是有一些了解的)。让大家在学校里可以对外面工业界的需求有一个大致的认识,在自己的兴趣和就业的导向之间找到一个平衡。 互联网公司的技术部门组织架构 从单体到分布式:后端工程师的分工 要理解一个(大型)互联网公司的组织架构,首先要理解一个产品的后端服务从 0 到 1 的发展过程。《大型网站技术架构》这本书对于这个过程有详细的解释。具体的看这篇博客。 大致来说就是这样的过程: 单机系统:Java/PHP/Node/Python + Linux + MySql + Nginx。 ==> 流量增大,数据库读写出现瓶颈,数据库分库分表 ==> 流量继续增大,单机部署无法应对高并发,于是集群部署,横向拓展 ==> 为了提高性能,抽出单独的缓存服务 ==> 分布式数据库/分布式存储 ==> 微服务架构 ==> 最终进化为适应于特定业务场景的复杂分布式架构 在小型的公司,一般就只有一个后端开发团队,里面没有非常明确的角色分配。服务,数据库,缓存,部署,都要自己来搞。当公司变大的时候,就会出现一些分工。 开发工程师主要负责开发服务,运维和 DBA 负责服务的部署,数据库的安全和效率。架构师负责设计整个应用的架构。 中间件/数据库:后端服务的基石 上一节说到互联网公司的后端架构最终演化为一个分布式的系统。那一个分布式的系统运行,需要许多中间件的支持。比如消息队列,分布式缓存,分布式数据库,微服务框架,容器编排调度等等。这些被其他服务调用的,负责通信/存储的服务,就是所谓的中间件。中间件可以说是连接两个服务的一个中介。 公司越来越大,就需要有人专门去维护这些公共组件,让开发工程师可以专注于业务逻辑的开发。比如维护自己的消息队列,数据库,缓存,RPC框架等等。在大公司中,开源的方案并不能满足业务需求,所以需要对开源的方案进行改进和创新。 上一节中的分工里没有提到的,就是中间件研发的工程师。这些工程师在一个大型的互联网公司中是非常重要的。 数据平台:公司决策的引擎 科技公司在运行中会产生各种各样的数据,比如用户行为数据,订单数据,访问记录数据。还有服务器的运行状态等等数据。这些数据都是一种资产,是公司来了解业务情况的一个窗口。 大数据这个方向其实已经非常成熟了,它主要包括数据的搜集,清洗,加工,存储,计算,分析。在这个链路上,需要很多工程师的努力。我们需要工程师来进行数据的清洗和加工,需要工程师来搭建数据仓库,数据加工平台,数据分析平台。需要工程师来优化数据实时和离线计算的引擎。需要工程师来写各个终端的数据上报服务。 所以大型的互联网公司都会有一个数据部门,让数据产生价值。 运维/安全:保证服务的高可用和安全性 传统意义上的运维负责公司服务器的管理,生产环境的稳定性,公司机房的设计和日常维护。大型公司中,还会涉及异地多活等等高可用方案。 运维这个角色现在也叫运维开发(DevOps)或者 SRE(网站稳定性工程师)。通过脚本和工具进行自动化的运维。比如开发要使用数据库,运维就搭建一个多副本的数据库集群,并且让数据库可以自动备份,还写了一个平台来访问和管理数据库。又或者有一个基础软件,需要安装在 500 台机器上。运维就开发了一个软件,可以让大家一键部署。 安全则是一个很大的领域,主要负责产品的信息安全。公司里一般都会有安全部。 一个大型科技公司的技术部门组织架构:从技术部到中台 小型公司一般只有一个技术部,里面分前端后台等角色。大公司一般是按业务来划分部门。比如电商业务,金融业务,零售业务等等。所谓的业务最终会落地到一个实际的产品中,比如淘宝。一个业务部门可能会同时运营多款产品。 这里需要引入一个概念,叫中台。和中台相对的就是前台。前台部门就是业务部门,直接开发面向消费者的产品的。中台部门就是负责给前台部门输送弹药的。负责支持前台部门。这里的支持不是帮前台部门写业务代码,而是提供一些基础的服务。比如前文里说到的中间件和数据库,还有数据平台。 我们在写产品的时候,会有一个抽象公共代码的过程。把多个模块中公共的部分抽出来。那在公司的运营中,我们也可以把公共的职能抽出来,变为中台。 每个业务部门自己去维护自己的中间件或者数据库,或者搭建数据平台,会造成很大的资源浪费。对部门之前的合作也不利。一个科技公司,统一自己内部的技术栈和基础设施,有几点好处: 方便内部的技术交流和合作 营造统一的技术氛围,利于公司内部的认同感 方便对外输出技术 降低各部门开发的成本 其实中台战略的好处是显而易见的,让专业的人做专业的事,前台负责业务上攻城略地,中台部门负责中台能力的搭建,支持前台业务。 按上述的模式设计的一个虚构的公司 foobar 的技术部门组织架构是这样的: 需要留意的是,本文里介绍的组织架构只是一个高度理想化的模型。现实中的大型科技公司的组织架构有很多种类。有的公司没有统一的中台部门,中台部门的职能被分散到各个业务部门中。只不过小前台+大中台是本文所推崇的一种组织架构。 职位概览 研发(Java/C++) 支持各种系统(面向消费者的,或者内部系统)的研发。基础平台,人工智能,数据平台也需要有平台来展现,所以也需要研发工程师。 简单的说就是写接口的。 数据研发 企业的数据中台,从数据的采集,加工,计算,存储,分析,都需要熟悉大数据相关技术(数据仓库/实时计算/离线计算/数据清洗)的工程师来参与。 基础平台研发(Java/C++/Go) 数据库开发,网络(CDN,机房网络),分布式存储,云计算(虚拟化,操作系统),云计算网络(SDN),中间件(消息队列,缓存,分布式服务框架)...

September 14, 2018

Webpack 4 配置最佳实践

Webpack 4 发布已经有一段时间了。Webpack 的版本号已经来到了 4.12.x。但因为 Webpack 官方还没有完成迁移指南,在文档层面上还有所欠缺,大部分人对升级 Webpack 还是一头雾水。 不过 Webpack 的开发团队已经写了一些零散的文章,官网上也有了新版配置的文档。社区中一些开发者也已经成功试水,升级到了 Webpack 4,并且总结成了博客。所以我也终于去了解了 Webpack 4 的具体情况。以下就是我对迁移到 Webpack 4 的一些经验。 本文的重点在: Webpack 4 在配置上带来了哪些便利?要迁移需要修改配置文件的哪些内容? 之前的 Webpack 配置最佳实践在 Webpack 4 这个版本,还适用吗? Webpack 4 之前的 Webpack 最佳实践 这里以 Vue 官方的 Webpack 模板 vuejs-templates/webpack 为例,说说 Webpack 4 之前,社区里比较成熟的 Webpack 配置文件是怎样组织的。 区分开发和生产环境 大致的目录结构是这样的: + build + config + src 在 build 目录下有四个 webpack 的配置。分别是: webpack.base.conf.js webpack.dev.conf.js webpack.prod.conf.js webpack.test.conf.js 这分别对应开发、生产和测试环境的配置。其中 webpack.base.conf.js 是一些公共的配置项。我们使用 webpack-merge 把这些公共配置项和环境特定的配置项 merge 起来,成为一个完整的配置项。比如 webpack....

June 19, 2018

Iris + Casbin 权限控制实战

在木犀的 PaaS 云平台的设计中,需要有一个细粒度比较小的权限控制系统。不同用户对不同的资源,拥有不同的权限。土办法已经不管用了,我们需要更系统,更规范的权限控制系统。本文讲的就是如何将权限控制库 Casbin 接入 Iris Web 框架。 Iris 中间件机制简介 Iris 这个框架是基于中间件的,和 Nodejs 的 Koa 和 Express 很像。所谓中间件机制,就是一个请求到达之后,会生成一个上下文信息,里面包含了这个请求的一些信息。然后我们依次调用中间件函数,把上下文对象作为参数传入。需要注意是中间件函数的调用是嵌套的,在中间件函数中我们可以调用 ctx.Next() 方法,进入下一个中间件函数。当最后一个中间件函数返回时,之前调用过的中间件会依次返回。这个数据流被形象的称为“洋葱模型”。 一个典型的中间件是这样的: func middleware(ctx iris.Context) { // get info from context requestPath := ctx.Path() // set info with context ctx.Values().Set("info", shareInformation) // call next middleware ctx.Next() } 我们可以在中间件中读取 ctx 结构,根据上面附带的信息,我们可以做一些针对性的事情。 一个常用的中间件场景就是访问控制。我们可以根据 ctx 上带的用户信息,来查看用户的权限,如果用户没有要访问的资源的权限,我们就拒绝这次访问。比如这样: func auth(ctx iris.Context) { // check if user has permission if !c.Check(ctx.Request()) { ctx.StatusCode(http.StatusForbidden) // Status Forbiden ctx.StopExecution() return } ctx....

May 14, 2018

如何阅读大型前端开源项目的源码

目前网上有很多「XX源码分析」这样的文章,不过这些文章分析源码的范围有限,有时候讲的内容不是读者最关心的。同时我也注意到,源码是在不断更新的,文章里写的源码往往已经过时了。因为这些问题,很多同学都喜欢自己看源码,自己动手,丰衣足食。 这篇文章主要讲的是阅读大型的前端开源项目比如 React、Vue、Webpack、Babel 的源码时的一些技巧。目的是让大家在遇到需要阅读源码才能解决的问题时,可以更快的定位到自己想看的代码。授人以鱼不如授人以渔,希望大家可以通过这篇博客,了解到阅读大型前端项目源码时的切入点。在之后遇到好奇的问题时,可以自己去探索。 问题驱动——不要为了看源码而看源码 首先我们要明确一点,看源码的目的是什么? 我个人的意见是,看源码是为了解决问题。开源项目的源代码并没有什么非常特殊的地方,也都是普通的代码。这些代码的数量级一般都挺大,如果想是从源码中学到东西,直接浏览整个 Codebase 无疑是大海捞针。 但如果是带着问题去看源码,比如想了解一下 React 的合成事件系统的原理,想了解 React 的 setState 前后发生了什么,或者想了解 Webpack 插件系统的原理。也有可能是遇到了一个 bug,怀疑是框架/工具的问题。在这样的情况下,带着一个具体的目标去看源码,就会有的放矢。 看最新版的源码 之前看到一种说法,看源码要从项目的第一个 commit 开始看。如果是为了解决前文中对框架/工具产生的困惑,那自然要看当前项目中用到的框架/工具的版本。 如果是为了学习源码,我也建议看最新的源码。因为一个项目是在不断迭代和重构的。不同版本之间可能是一次完全的重写。比如 Vue 2.x 和 React 16。重构导致了代码架构上的一些变化,Vue 2.x 引入了 Vritual DOM,Pull + Push 的数据变化检测方式让整个代码的结构变的更清晰了,所以 2.x 的代码其实比 1.x 的更容易阅读。React 16 重写了 Reconciler,引入了 fiber 这个概念,整个代码仓库结构也更清晰,所以更推荐阅读。 前置条件 看源码怎么看,当然不能一把梭了。 看源码之前需要对项目的原理有一个基本的了解。所谓原理就是,这个项目有哪些组成部分,为了达到最终的产出,要经过哪几步流程。这些流程里,业界主流的方案有哪几种。 比如前端 View 层框架,要渲染出 UI,组件要经过 mount、 render 等等步骤。数据驱动的前端框架,在 mounted 之后,就会进入一个循环,当用户交互触发组件数据变化时,会更新 UI。其中数据的检测方式又有分 Push 和 Pull 两种方案。渲染 UI 可以是全量的字符串模板替换,也可以是基于 Virtual DOM 的差量 DOM 更新。 又比如前端的一些工具,Webpack 和 Babel 这些工具都是基于插件的。基本的工作流程就是读取文件,解析代码成 AST,调用插件去转换 AST,最后生成代码。要了解 Webpack 的原理,就要知道 Webpack 基于一个叫 tapable 的模块系统。...

May 1, 2018

Headless Chrome long image capture issue

The problem Recently I had received complaint about my capture service not export complete image. It seems that this problem only occurs when the page’s is extremely long. The broken image is like this: Chromium’s limit So I Googled for the problem and I found a lot issues on Github that target the same problem. When reading throught this issue, I got the fact that this problem is caused by Chromium’s limit....

February 12, 2018

Liso源码阅读笔记

本文介绍了CMU 15-441的课程项目Liso的一个实现。主要介绍了请求流程、Client状态机模型、Dynamic Buffer数据结构等等。SSL相关的部分没有涉及。 实现 我看的是这位同学的实现。 一个HTTP静态文件请求的流程 首先我们拿一个简单的静态文件请求来梳理一下Liso的流程。在liso.c的main函数中,有这个服务器的主循环,这个循环每次做的事情就是用select来查询I/O事件。select之后,我们首先做的是查看listenfd是不是有read事件,如果有就accept这个连接。然后把连接加入到连接池里。一个连接的初始状态是READY_FOR_READ。 listenfd是连接池中的第一个item,也是一直存在于连接池中的一个描述符。 在循环的最后,我们会调用handle_clients对连接池中可读和可写的描述符依次进行处理。具体的方式就是遍历连接池,如果一个连接是可读的,并且状态是READY_FOR_READ,我们就读取这个连接中的数据。 每次从每个连接读取的数据是固定的长度,比如4KB。这是为了控制I/O的粒度,不让server在一个连接上花太多时间,使得新的连接请求被阻塞。 我们把收到的数据,放到client_buffer里面。然后看一下HTTP的Header是不是已经读取完全了(调用handle_recv_header判断读取到的数据中是否有"\r\n\r\n")。如果HTTP的header还没有读取完全,那就继续留在READY_FOR_READ状态,等待下一次循环。 如果Header已经读取完全了,我们就调用handle_http_request处理请求,在处理请求时,我们把要返回的数据写在client_buffer里,然后调整这个client的state到READY_FOR_WRITE。 在handle_clients的循环中,如果我们发现一个client的状态是READY_FOR_WRITE,我们就会把client_buffer里相关的数据写到客户端,然后根据这个连接的ConnectionHeader选择关闭或者保留这个连接。 这就是一个简单的HTTP静态文件请求的流程。 连接池的数据结构 typedef struct { /* Client pool global data */ fd_set master; /* all descritors */ fd_set read_fds; /* all ready-to-read descriptors */ fd_set write_fds; /* all ready-to_write descriptors */ int maxfd; /* maximum value of all descriptors */ int nready; /* number of ready descriptors */ int maxi; /* maximum index of available slot */ /* Client specific data */ int client_fd[FD_SETSIZE]; /* client slots */ dynamic_buffer * client_buffer[FD_SETSIZE]; /* client's dynamic-size buffer */ dynamic_buffer * back_up_buffer[FD_SETSIZE]; /* store historical pending request */ size_t received_header[FD_SETSIZE]; /* store header ending's offset */ char should_be_close[FD_SETSIZE]; /* whether client should be closed when checked */ client_state state[FD_SETSIZE]; /* client's state */ char *remote_addr[FD_SETSIZE]; /* client's remote address */ /* SSL related */ client_type type[FD_SETSIZE]; /* client's type: HTTP or HTTPS */ SSL * context[FD_SETSIZE]; /* set if client's type is HTTPS */ /* CGI related */ int cgi_client[FD_SETSIZE]; } client_pool; 每个socket连接在建立之后都会被放到client_pool里面。连接池在概念上就是一个大的数组。实现的时候,我们把每个连接相关的属性各自设置为一个相同大小的数组。每个item的属性就是属性数组中相应index的值。比如我们可以通过client_pool->client_fd[i]拿到第i个请求的fd。其他属性也是类似的。...

February 12, 2018