Ruby 从头开始写一个 Rails-like web 框架

suffering · 2014年09月12日 · 最后由 lzm420241 回复于 2016年04月22日 · 9271 次阅读
本帖已被管理员设置为精华贴

最近阅读Rebuilding Rails时对自建ruby web framework有了兴趣,就模仿 rails 的功能写了个。

目的不是再造个轮子,我也没有这个能力,主要是想在自己写框架的过程中理解 rails 的基本工作原理,提升一下自己的水平。这只是一个演示项目,没有半点的实际使用价值,唯一的作用就是便于阅读,在阅读的过程中可以了解一些 RAILS 的基本原理。

项目地址:https://github.com/suffering/learnmvc

贴上部分 README:

目标

这个项目的目的不是重写一个Ruby web framework, 而是在自己开发一个Rails-like framework时,试着去理解一个ruby mvc style web framework的一些基本原理。

为什么需要

Rails的源码就是头恐怖的怪兽,无数的童鞋倒前仆后继地打开源码,而后倒下。

Rails看起来就像魔法,运行rails s, 而后一切就开始运行了。这些行为都是从哪里开始的?客户端的请求到达后它如何接收?如何处理?为什么在 controller 里可以调用 Model 里定义的模型?为什么 controller 里的实例变量可以在 view 中运用?Rails 是如何将 erb 文件渲染而后生成内容的?它如何与 layout 协作并返回?如此种种。

对于新手来说,这个就是魔法,虽然很疑惑,却是没有时间也没有能力去寻找这些答案。同时,新手也不需要去理解这些因为哪怕不理解,只要懂得 rails 的规则,把相应的东西写在相应的地方 (MVC), rails 就会将它们组合在一起,生成内容并打包发送。事实上,很多人即使不了解这些,只要对 rails 足够了解,经验充足,也可以写出很好的项目来。

但是这样是不够的,只有更多地理解 rails 的内部原理,才能更好地使用它,写出灵活高效的代码。

已经实现的功能

  • 以 rails MVC 的方式组织代码,以 rails 的规则命名 controller, view, model 等之后,相互之间可以协作。
  • 自定义 route, 使用match, resources, get, post, put, delete等方法自定义路由。如 match '/products/:id' => 'products#show'.
  • 自定义 database.yml, 使用activerecord作为ROM
  • 实现 layout yield, content_for, yield, render, redirect_to 等等
  • 实现静态文件 serve.

注:已实现的只是 rails 的九牛一毛。展示的只是基本的调用链。及 MVC 基本的结合方式。许多功能诸如 session, cache, secure, test, configable 等都没有实现。

基本逻辑

  1. 一切从 Rack 开始。几乎所有的 ruby web framework 都是 rack app. Rack 对象响应 call 方法,返回三元素的 array, 分别是 status code, header, content body. 只要你的项目符合以上三个要求,就是一个合法的 rack app. 可以运行它,在浏览器访问,看到完整的响应内容。所以,主流程即是 request 与 response 的地程。我们所做的事情就是在中间加入一些自己的东西。
  2. http request 到达 web server 后会即被 rack 封装,而后你得到一个 env 对象。它包含了客户的请求类型 (get/post/put/delete/..), 请求的地址 (env['PATH_INFO'], QUERY_STRING 等等.
    1. 通过分析 env, 我们知道客户的请求是指向哪个 controller#action. 而后查看路由表我们的 app 能否响应此请求。
    2. 路由表在新建 app 对象时通过 routes 方法来定义,具体的做法是接受一个 block, block 内调用match, get, post等方法时,生成路由规则加入路由表。路由表里包含路径字符串的匹配正则,controller, action, params 等等。
    3. 参照上一条,在路由规则中检查 env['PATH\_INFO'], 若匹配,就知道了指向哪个 controller 的哪个 action, 以及其 params. 通过 ctrl_const = Object.const_get(params[:controller].capitalize)来得到相应的 controller.
    4. 通过 ctrl_const.new(env).call(params[:action]) 可以调用到相应的方法。
    5. 到这一步,已经初步描述了一个请求从客户端到服务器端并指向需要的controller#action的基本过程。也即处理 request 的过程完成。
  3. Response 的过程:
    1. 在 ctrl_const.new 上调用 action 后新的对象内就会拥有相应的实例变量。这时通过Tiltgem, 按规则生成目标 view 的名字,找到它,而后 render, render 时将 self 作为 scope 传入。至此 view 里可以调用 action 里所有的实例变量。Tilt.new(view).render self 得到了应该返回的 html 的内容。
    2. 上一步render得到的只是Controller#action对应的view, 需要将它交给 layout 处理。同样的使用Tilt, 将上一步得到的 partial view 放到 block 中提交过去。这样 layout 中的<%= yield %>关键字生效。至此,得到完整的 html 内容。
    3. Rack 要求调用 call 后的返回的值是一个三值的 array, 分别是 [status code, head content, body].上一步得到的是 html 内容就是 body 部分。

如何使用和阅读本项目的源码

git clone https://github.com/suffering/learnmvc.git
cd learnmvc
#在编辑器中打开
cd srbmvc
bundle install
cd ../simpleapp
bundle install
rackup -p 3002
#通过rackup方式打开后, 代码在更改后不会自动重载, 可以考虑使用rerun
# gem install rerun
# rerun 'rackup -p 3002'
#以这种方式运行, 对任何文件的修改都会重载代码. 简单模拟rails的development mode.

Demo app 运行后,可以看到项目正常运行。

查看simpleapp的源码,你会看到它的基本结构与 rails app 基本相同。

查阅srbmvc的源码,按前文的基本逻辑栏来查看代码,观看其调用链。代码中有少量的注释,没有解释具体的细节,只简单标注出此方法实现的目的与功能。此部分代码的关键点在于从 request 开始后的调用链,route 规则的指定与检查,controller#action的定位 以及 render view 部分。其他皆渣。

请将simpleappsrbmvc结合来阅读。

最好的学习方式是,看完后,自己写一个。在写的过程中,会发现很多以前注意不到的细节。在实现 rails 的各种功能的过程中,你必须对 mvc 的协作方式进行深入思考。人在思考的过程中成长。

感谢@wikimo同学帮忙纠正错误~

我来消灭 0 回复,谢谢分享^_^

顺手把 angular 搞进去

#2 楼 @huobazi , 还没有支持 API 返回呢。

40 美刀~~~多厚?

#4 楼 @limkurn , 我只看了 Rebuilding Rails 的免费部分,前几章而已。穷啊,40 美刀买不起。 参考了这本书的初始架构,其他的是我自己写的呢。

此项目可部署:demo: http://learnmvc.zhuboliu.me/ 其中,products链接处不能访问,是因为这里需要引用数据库。

#6 楼 @suffering 20 ~ 30 还能接受~~~

至今 Rack 还未吃透~

$40,好贵的样子

匿名 #10 2014年09月14日

👏

http://owningrails.com/ 也在讲同样的事情,不过是在线课程,讲师是 Thin 的作者 @xdite 有推荐 http://wp.xdite.net/?p=2407

#11 楼 @jasl 这个到更贵啊,$400+ 简直伤不起。

#12 楼 @suffering 我感觉那课的精髓在于和作者的交流,单纯的实现框架(只实现核心 DSL,不考虑性能)并不是很难

刚接触 ruby 的时候就被告知,核心是 rack,一直在用,但是从不深入。。。

教我 ruby 的是一个叫罗昕的~~~哇嘎嘎

我找到个老版本的(2013.8.4),不过内容还很全(137 页),大家可以下载看看,链接地址https://github.com/moon2l/Rebuilding-Rails

#15 楼 @moon2l ,3Q, 一直在找,都没有找到。谢谢啊谢谢 😄

#14 楼 @badboy 那你还不过来参会 - -

#16 楼 @suffering #15 楼 @moon2l 我问作者要到一个优惠,然后 4 个人合买了。。。

有时真的挺对 Ruby 底层代码感兴趣,但是却没有这个实力去掌控它

#20 楼 @hammer 谢谢您提供的资料!

#20 楼 @hammer 这个链接讲 ruby 的原理,并非 rails 的,从 LZ 写的文章可以看书 LZ 更希望能理解 rails 的源码,不知道是不是我理解错了?我想更多的人是希望理解 rails 里面的机制把

需要 登录 后方可回复, 如果你还没有账号请 注册新账号