构建自己的MVC框架(Ruby语言实现)-- 开篇

简介: 构建自己的MVC框架(Ruby语言实现)-- 开篇

0. 开发Rails

为什么要重建Rails?

What I cannot create, I do not understand.

Feynman


了解软件的最深层次的实现可以让你掌握它。它是你无法假装的一种特殊的完成式。你必须 足够了解它,然后才能重新构建它。那么假如你真的重建了它会怎么样?这种努力值得吗?

这本书将引导你从零开始构建一个和Ruby on Rails类似的框架(不知道的同学可以自己百度一下~ 世界上最早的MVC框架,影响了一代Web 框架),使用同样的Ruby特性和结构,它们让 Rails变得如此有趣、好玩。

Ruby onRails 以“魔法”著称。当你真的使用这些Ruby特性来构建和Rails相似的框架时,你会 发现这些没法并不是滥用,它们都是有道理的。

另外,Ruby on Rails是一个固执己见的框架;Rails团队自己也这样的观点高调的宣扬Rails。Rails也是惯例先于配置的实践框架,假 如你有不同的想法时怎么办?你也可以构建一个Rails一样的框架,然后你可以能耗费大量的时间,来按自己的想法添加各种特性。

总之,无论你是想深入理解Rails,还是想要构建你自己的个人版本,这本书都可以帮上忙。

谁应该重建Rails?

你需要有一点Ruby基础。假如你构建过几个小的Ruby应用程序,或者一个中型的Rails应用, 你应该可以了。当你阅读的时候,你参考一下鹤嘴锄书(国内可以搜《Ruby编程》)也可以。你应该可以不用太麻 烦就能用Ruby写一些基本的程序。

假如你想要复习一下Rails,Michael Hartl的指南非常好:“http://ruby.railstutorial.org/ruby-on-rails-tutorial-book”。HTML版本是免费的,或 者你可以购买电子版或视频。本书用到的一些概念,假如你已经在Rails里见过的话,对这些 概念就会更明白。

在大部分章节里,我们将使用一点Ruby魔法。我们会边学习,边解释这些魔法。这些魔法都 很好理解。只是会让你觉得Ruby还让我这样用!

亲手实现

每章都是关于在和 Rails 类似的框架里构建系统,我们喊它为Rulers(就像Ruby on Rulers)。Rulers比最近版本的的Rails更简单。但是一旦你实现了这个简化版,你就知道复杂的版本做些什么了,以及它们是怎么工作的。

后面的文章里有一个源代码的链接 -- 那是本书每章前一章结束时标准的Rulers的代码。你可以下载源代码,然后动手实现任何你觉得好奇的一章。

在每章的末尾是建议的特性和练习。略过它们很容易,它们是可选的。但是假如你在每章结束后停下来,然后想想在你框架里你还想实现那些功能,你将会收获更多。你想要知道关于 Rails的那些实现?那些是Rails没有的,而你自己却非常想要的功能?Sinatra(另一个Ruby 框架)有一些精彩的内容吗?你想要要添加特性就是最好的特性!

作弊

你可以从GitHub下载下一章的示例代码,不用一章一章的敲代码。假如你自己敲代码的话就 会学到更多,敲错代码了,是的,然后苦逼的调试代码。但是假如有一些关卡你就是过不去 ,没关系,使用示例代码继续下一章。下次再看到的时候就会容易一些了。

让每件事情完美可能的解决可能需要几次才行。回到那些内容太多的代码和练习。跳过那些 ,后面再回去,再在它们上面花点时间。假如这本书的版本对你来说太难,你可以去看看对 应的Rails,或者其他简单一点的框架,如Sinatra。有时你只是需要看看不同版本的概念解 释。

哦,通常你不会得到练习的源代码,只有主要的章节才有。甚至对作弊的也留了一点挑战。不用客气。

在本章的最后是每个系统的Rails版本的源代码。阅读Rails源代码是可选的。但是,即使主 要的组件(ActiveRecord, ActionPack)也有25000行代码 -和其他大部分框架比较的话还是 挺短的,可读性也较高。通常你正寻找一些特殊的更小的组件,大概就是一百到一千行左右。

假如你花时间读好的源代码你也会试一个更好的Rails程序员。Rails代码是Ruby技巧的宝藏 ,也有一些很有趣的元编程示例。


环境搭建可以参考另一篇文章,0.5章开发环境设置 -- 构建自己的MVC框架

1. 从零到它工作了!

既然你已经安装好了,到了开始构建的时候了。像Rails一样,你的框架将是一个gem(Ruby 的库),gem就像其他语言的库,可以包含、引用。整本书中,我们的框架的名字都是Rules,即“Ruby on Rails”。

在本书的开始,有些命令你可能不太熟悉。没关系,跟着书在命令行敲进去就可以了。


开垦“荒地”

首先创建一个新的gem:

gem相当于其他语言的库,lib,或者jar包等。是一个其他程序员开发好以后打包好的源代码文件。


$ bundle gem rulers
create  rulers/Gemfile
create  rulers/Rakefile
create  rulers/LICENSE.txt
create  rulers/README.md
create  rulers/.gitignore
create  rulers/rulers.gemspec
create  rulers/lib/rulers.rb
create  rulers/lib/rulers/version.rb
Initializating git repo in src/rulers”

Rulers是我们创建的gem(库),在自动生成的rulers.gemspec文件里定义它所需的其他依赖 。在你的文本编辑器里打开那个文件。假如你喜欢,你可以个性化你的名字,gem的功能描述 等等。你可以像这样自定义多个部分:

# rulers.gemspec
gem.name    = "rulers"
gem.version = Rulers::VERSION
gem.authors = ["Singleton Ruby-Webster"]
gem.email       = ["webster@singleton-rw.org"]
gem.homepage    = ""
gem.summary     = %q{A Rack-based Web Framework}
gem.description = %q{A Rack-based Web Framework,
but with extra awesome.}

按照惯例,摘要(summary)和描述(description)差不多,就是更短一些。摘要通常只有一样,然而描述有时会长达4-5行。

记住一定要把这个文件里FIXMETODO这样的字符替换掉,gem build不喜欢它们,而且它们对用户也不友好。

你需要在底部添加依赖库。它们可能和下面的内容差不多:

gem.add_development_dependency "rspec"
gem.add_runtime_dependency "rest-client"
gem.add_runtime_dependency "some_gem", "1.3.0"
gem.add_runtime_dependency "other_gem", ">0.8.2”

每一行添加一个运行时依赖(在运行gem的时候需要)或者开发时需要的依赖(在开发或测试 的时候用到的gem)。目前,只需添加下面的:

gem.add_runtime_dependency "rack"

Rack也是一个gem,是你的框架和Ruby应用程序服务器,例如Mongrel、Thin、Lighttpd、Passenger、WEBrick或Unicorn,之间的接口。应用服务器是特殊类型的网页服务器,运行服务器程序,通常是用Ruby写的。在真实的生产环境,会在应用程序服务器前再运行像Apache或者NGinX这样的网页服务器。但是在开发环境我只运行应用程序服务器,其他的都不运行。幸运的是,应用程序服务器作为网页服务器效果也还不错。

我们会在控制器那章覆盖更多Rack的细节,然后在中间件(Middleware)再次覆盖一部分。现在你只需要知道Ruby通过Rack把HTTP请求变成在你的服务器上运行的代码。

修改lib/rulers/version.rb文件,将0.1.0改为0.1.1

让我们打包你的gem(译者注:Ruby是动态语言,不需要编译),然后安装它:

$ gem build reulers.gemspec
$ gem install rulers-0.0.1.gem

最终我们将从开发目录使用Bundler的技巧来使用你的gem。但是现在我们只是用简单的方法 来安装它。知道完成一个任务最简单的方法总是好的--当聪明的技巧不起作用的适合,你可 以回头使用它。

Hello World,或多或少

Rails就像你刚才打包的Gem,但是它运行的是什么程序?我们将从一个很简单的app开始,你可以提交最爱的名言,用户可以给它们打分。如果你用的是Rails,那么Rails自带生产新的APP的命令,不过我们现在只能手动构建。

先创建一个目录和一些子目录,注意目录结构:

$ mkdir best_quotes
$ cd best_quotes
$ git init
Initialized empty Git repository in src/best_quotes/.git/
$ mkdir config
$ mkdir app”

你也想使用你的自己写库rulers。添加Gemfile:

# best_quotes/Gemfile
source 'http://gems.ruby-china.com/'
gem 'rulers' #你创建的gem

然后运行“bundle install”来创建Gemfile.lock,确保所有的依赖都可以使用。

我们先构建一个小小的Rack程序。创建config.ru文件:

# best_quotes/config.ru
run proc {
  [200, {'Content-Type' => 'text/html'},
    ['Hello, World!']]
}

使用Rack创建Rulers

在你的Rulers的目录,打开lib/rulers.rb。修改如下:

# rulers/lib/rulers.rb
requrie 'rulers/version'
module Rulers
  class Application
    def call(env)
      [200, {'Content-Type' => 'text/html'},
        ['Hello from Ruby on Rulers']]
    end
  end
end

重新打包安装

$ gem build rulers.gemspec
$ gem install rulers-0.0.1.gem

现在切换到应用程序目录: best_quotes。

现在你可以使用Rulers::Application类了。在config目录下新建文件config/application.rb,然后添加以下代码:

# best_quotes/config/application.rb
require 'rulers'
module BestQuotes
  class Application < Rulers::Application
  end
end

“BestQuotes”对象应该使用你的Rulers框架,当你使用它的时候会显示“Hello from Ruby on Rulers”。为了使用它,打开你的config.ru,然后修改如下:

require './config/application'
run BestQuotes::Application.new

现在当你在命令行输入rackup -p 3001然后在浏览器输入http://localhost:3001,你应该看到“Hello from Ruby on Rulers!”。

你已经用你自己的框架创建了一个应用程序!

复习

在这章,你创建了一个可重复使用的Ruby库,作为gem。你把你的gem包含进示例应用程序。你也建立了一个简单的Rack程序,你可以使用Rackup文件config.ru来创建。你学到了Rack基本用法,然后把这些东西连接起来以便让它们工作。

从这里开始,你将逐渐添加一些新东西进去。但是这章是唯一的一次从白板开始,从什么也 没有到有一点功能。

厉害吧。

在Rails里

默认Rails包含不止一个,而是五个可以重复使用的gem。真正的railsgem仅有很少的代码。相反,它把请求分发给支持的gem。Rails本身只是把它们连接在一起。

Rails允许你改变许多组件 - 你可以使用不同的ORM,不同的测试库,不同的Ruby模板库或者不同的Javascript库。所以 描述对于严重自定义的程序来说不总是100%准确。

下面是基础的rails gem -- 没有依赖和库,只有rails本身基础的一部分内容。

  • ActiveSupport 是一个Ruby扩展库,它里面的方法不是专为Rails设计的。你会看见 一些不是Rail的库里也使用了ActiveSupport。ActiveSuppot包含了一些像Rails怎么变换单 词的单复数,或怎么把驼峰(CamelCase)名称转换成蛇形(snack_case)名称。它也包括了 比Ruby标准库本身显著提高的时间和日期支持。
  • ActiveModel 卷入你的模型的特性。它不止针对数据库 -例如,假如你想要某个模型的URL,ActiveRecord也可以帮你实现。它只是包装了许多不 同ActiveModel的实现来告诉Rails怎样使用他们。更宽泛的说,ActiveModel实现是ORM(见 ActiveRecord,下面提到),但是他们也能使用非关系型存储,像MongoDB,Redis, Memcached或者甚至只是本地机器存储。
  • ActiveRecord 是一个对象-关系映射(ORM)。这意味着它在Ruby对象和SQL数据库表之间 相互映射。当你在Rails里从SQL数据库查询或者写入数据,你都是通过ActiveRecord。ActiveRecord也实现了ActiveModel。ActiveRecord支持MySQL和SQLite、也支持JDBC、 Oracle、PostgreSQL和许多其他的关系型数据库。
  • ActionPack 负责路由 - 输入的URL到Rails里的控制器(Controller)和动作(Action) 之间的映射。它也建立你的控制器和视图,然后通过它的控制动作引导请求,然后渲染视 图。这里中的一些,ActionPack通过Rack来实现。模板引入其他的外部的gem来渲染自己, 像Eruby渲染.erb模板、Haml渲染.haml模板。ActionPack也处理动作 - 或视图为中心的功能,如视图缓存。
  • ActionMailer 用来发送邮件,尤其基于模板的email。它具有你期望Rails email能具有的 功能,有控制器、动作、和“视图” - 对于email来是说基于文本的模板,不是普通的网页模板。

当然,Rails里不是啥都有。

一些你在这章里构建的功能是在程序里,不是在Rulers里。继续向前,创建新的Rails 4 应用 - 输入 rails new test_app。假如你看看config/application.rb,你将看见Rails 建立了一个Rails Application对象,非常像Rulers Application对象。现在该是打开config目录,看看Rails Application默认生成了些什么。你看到一些现在觉得更合理的代码了吗?

练习

练习1. 重新加载Rulers

让我们给Rulers框架添加一点调试代码。

# rulers/lib/rulers.rb
module Rulers
  class Application
    def call(env)
      `echo debug > debug.txt`;
      [200, {'Content-Type' => 'text/html'},
         ['Hello from Ruby on Rulers!']]
    end
  end
end

当这个运行时,它应该在best_quotes文件夹下创建一个名为debug.txt的文件,也就是你运 行rackup的那个目录。

试着重启你的服务器,刷新你的浏览器页面“http://localhost:3001”。但是你找不到debug 文件!

试试重新打包gem,然后重新安装它(gem build rulers; gem install rulers-0.0.1.gem)。刷新浏览器。你仍看不见它。最后,再次重启你的服务器,刷新浏览器。现在,你终于看见debug文件了。

Rails和Rulers都很难调试。在第三章我们将看到Bundler的:path选项,可以让我们调试容易 点。现在你需要重新安装gem和重启rack服务器来让新的Rulers代码执行。当方便的方式行 不通的时候,你也知道怎么用笨方法。

练习2. 你的库的库

你可以开始一个简单的可复用的库,就像ActiveSupport。当程序使用你的Rulers gem,然后require它(“require rulers”),程序会自动得到那个文件里所有方法。试试添加lib/rulsers/array.rb的文件, 然后加入以下代码:

# rulers/lib/array.rb
class Array
  def sum(start = 0)
    inject(start, &:+)
  end
end

你以前见过“&:+”吗?它是个有趣的技巧。“:+”意思是“符号+”就像“:foo”意思是“符号foo”。“&”意思是“当作块传递”-- 在圆括号里的代码快通常跟在后面。所以你正把符号当作代码块 来传递。Ruby知道把符号转换为proc,然后调用它。当你用“加”的时候,你得到“把这些加 起来”。

现在,把require 'rulers/array'添加到lib/rulers.rb的顶部。它会在所有Rulers应用里包含它。

在你打包gem前,你需要进入rulers目录,运行git add -A(git add -A; gem build rulers.gemspec; gem install rulers-0.0.1.gem)。那是因为rulers.gemspec实际调用git来查找包含进你gem里的文件。你可以在rulers.gemspec文件里看到一行:

gem.files = `git ls-files`.split($/)

“git ls-files”仅仅显示git知道的文件 -- split只是为了得到输出的每一行。加入你创建 了一个新文件,确信你重新打包前运行了git add -A,否则你看不到它!

现在有了新的rulers/array.rb文件,任何保含了Rulers的程序都能写[1, 2, 37, 9].sum, 得到数组的和。继续,添加更多的那些使用你的框架的程序可能会有用的方法。

练习3. 早点测试,经常测试

因为我们正创建Rack应用,rack-test gem可以很方便的测试它。让我们来用用。

把rack-test添加到你的gemspec文件里中的开发(不是运行时runtime)依赖:

# rulers/rulers.gemspec, 底部附近
# ... (译者注:...表示省略,下文同,不再做说明)
gem.add_runtime_dependency 'rack'
gem.add_development_dependency 'rack-test'
gem.add_development_dependency 'test-unit'
end

(在Ruby 2.0或者更高版本,你需要显示地声明Test/Unit依赖--不再自动包含了。)

当你有了Gemfile的适合,为什么还要使用Gemspec?应用程序和依赖你的gem的库考虑 gemspec,所以这是个好习惯。目前,它是开发依赖,所以没太大关系。

现在运行bundle install来确保你已经安装了rack-test。我们为Rulers添加一个可用的测试。后面你会写 更多的测试。

创建测试目录:

# 在Rulers的目录下
$ mkdir test

现在我们将创建测试辅助方法:

# rulers/test/test_helper.rb
require 'rack/test'
require 'test/unit'
# Always use local Rulers first
d = File.join(File.dirname(__FILE__), '..', 'lib')
$LOAD_PATH.unshift File.expand_path(d)
require 'rulers'

这里唯一让人惊奇的一点就是$LOAD_PATH魔法。它确保require的‘rulers’将require当前目 录本地的而不是,就是说,你作为gem安装的那个。它通过在$LOAD_PATH前添加本地路径,以 便在任何别的路径前提前检查本地路径。

我们也expand_path(扩展路径)以便它是绝对路径,而不是相对路径。这点很重要,因为有 可能改变当前目录。

测试不同的本地变化对一个已经安装了gem可能有点麻烦--你已经安装了什么?正使用什么?通过显示的在本地路径前添加,你可以确定优先使用本地代码,这个代码在使用前不必安装。

现在你需要测试,我们把它放在test_application.rb里:

#rulers/test_application.rb
require_relative 'test_helper'
class TestApp < Rulers::Application
end
class RulersAppTest < Test::Unit::TestCase
  include Rack::Test::Methods
  def app
    TestApp.new
  end
  def test_request
    get '/'
    assert last_response.ok?
    body = last_response.body
    assert body['Hello']
  end
end

require_relative只是意味着“require,但是从这个文件的目录检查,不是加载路径”。它是一个有趣,简单的技巧。

这个测试创建了新的TestApplication类,创建了一个实例,然后在那个实例上get '/'。它用last_response.ok?检查错误,检查返回的body包含“Hello”。

为了运行测试,输入“ruby test/test_application.rb" 。它应该运行测试,显示一个和下 面类似的消息:

# Running tests:
.
Finished test“in 0.007869s, 127.0810 tests/s, 254.1619 assertions/s.
1 tests, 2 assertions, 0 failures, 0 errors, 0 skips”

上面的那行get '/'也可以是post '/my/url',假如你更偏好这种方式,或者任何其他 HTTP方法和URL。

现在,写起码一个以上的测试。

2. 你的第一个控制器

在这章你将写你的第一个控制器,也开始明白Rails怎样路由请求。

你已经有了很基础的gem和应用程序,gem安装到本地。假如你没有这些,或者你不喜欢你在 第一章写的代码,你可以下载示例代码。

我们将依据本书的每章依次在版本号上加1。假如你正用你自己的代码,你可以修改它,或者 不修改,都可以。

为了改变gem的版本,打开rulers/lib/rulers/version.rb,把常数从“0.0.1”改到“0.0.2”。下次你重新安装你的gem的时候,你需要输入“gem build rulers.gemspec;gem insall rulers-0.0.2.gem”。你应该删除rulers/rulers-0.0.1.gem,这样你就不会错误地安装和运 行旧的版本。

示例源代码

所有章节的示例源代码均保存在Github上:http://github.com/noahgibbs/rulershttp://github.com/noahgibbs/best_quotes

一旦你复制了仓库,在每个目录运行“git checkout -b chapter_2_mine chapter2”来创建一个新的名为“chapter_2_mine”的分支。

关于Rack

上一章Rack的复杂的返回值可能需要好好解释一番。所以让我们解释一下吧。这是其一:

[200, {'Content-Type' => 'text/html'},
  ['Hello!']]

让我们把它分解开来。第一个数字,200,是HTTP状态码。假如你返回了404,然后浏览器将 显示404信息--找不到页面。假如你返回500,浏览器将提示服务器错误。

下一个hash是header。你可以返回所有的header来设置cookie,改变安全设置和徐错别的东 西。现在对我们来说最重要的一个是'Content-Type',它必须是‘text/html’。它告诉浏览器 按照HTML来渲染文本,而不是JSON、XML、RSS,或者其他别的东西。

最后,是内容。这里我们只是包含字符串的部分。所以浏览器将显示“Hello!”

不久,我们将解释Rack的“env”对象,它是一串有趣的hash值。现在,你需要知道的是那些之中一个名为PATH_INFO的值,它是URL中服务器名称后面不包括查询参数的的那部分。它也是Rails应用程序里告诉Rails使用哪个控制器的那个动作的部分。

路由

一个请求到达你的网页服务器或应用程序服务器,Rack把它传递给你的代码。Rulers将需要 路由请求 -- 那意味着它从那个请求了取走URL,回答了这个问题,“什么控制器和什么动作处理这个请求?”我们将从简单的路由的开始。

具体来说,我们准备从Rails的默认路由开始。那意味着形如“http://host.com/category/ action”的URL将被路由到CategoryController#action。(译者注:按照Rails的惯例,通常 控制器使用控制器名称最后的名词的复数形式,即CategoriesController。)

在Rulers的目录下,打开lib/rulers.rb

# rulers/lib/rulers.rb
require "rulers/version"
require "rulers/routing"
module Rulers
  class Application
    def call(env)
      klass, act = get_controller_and_action(env)
      controller = klass.new(env)
      text = controller.send(act)
      [200, {'Content-Type' => 'text/html'},
        [text]]
    end
  end
  class Controller
    def initialize(env)
      @env = env
    end
    def env
      @env
    end
  end
end

我们的Application#call方法现在从URL得到了控制器和动作,然后创建了一个新的控制器,然 后把它发送给动作。对于像“http://mysite.com/people/create”这样的URL,你会希望 klass值为PeopleController,action值为“create”。我们将在rulers/routing.rb里实现, 如下。

控制器只是保存我们给出它的环境。我们将在后面使用它。

现在在lib/rulers/routing.rb里:

# rulers/lib/rulers/routing.rb
module Rulers
  class Application
    def get_controller_and_action(env)
      _, cont, action, after =
      env['PATH_INFO'].split('/', 4)
      cont = cont.capitalize # 'People'
      cont += 'Controller'   # 'PeopleController'
      [Object.const_get(cont), action]
    end
  end
end

这是很简单的路由,所以我们只是让控制器和动作尽可能的简单。我们把URL从“/”分开。“4” 是说“分开不要4次”。所以split从第一个斜线前分开,给“_”分配了空的字符串,然后是控 制器,然后是动作,然后是剩下的一起分给最后一个变量after。现在我们把第二个“/”后面 的全扔了 - 但是它仍然在环境里,所以它不是真正的被抛弃了。

“const_get”方法是Ruby魔法的一员 - 它的意思是查找任何大写字母开头的名称(译者注:在Ruby中,首字母大写意味着是常量)- 这里,就是指你的控制器类。

另外,有时你会看见下划线,常常表示“一个我不关心的值”, 就像上面我所做的那样。它 实际上是个普通的变量,如果你喜欢,你可以使用它,但是许多Rubyist(Ruby程序员的爱 称)喜欢用它表示“一些我正忽视或不想要的东西”。

现在,你要在best_quotes里创建一个控制器。在app/controllers的目录下,创建一个名 为quotes_controller.rb的文件:

# best_quotes/app/controllers/quotes_controler.rb
class QuotesController < Rullers::Controller
  def a_quote
    "There is nothing eigher good or bad" +
    "but thinking makes it so."
  end
end

虽然看起来像一个正经的控制器的,即使看上去不是完全和Rails的控制器一样。你需要手 动把它添加它到程序里,因为你还没有为控制器实现Rails那样魔法般的自动加载文件功能。所以打开best_quotes/config/application.rb。你要在声明你的程序前在require 'rulers'后面添加下面一行:

# best_quotes/config/application.rb (片段)
$LOAD_PATH << File.join(File.dirname(__FILE__),
'..', 'app',
'controllers')
require 'quotes_controller'

$LOAD_PATH让你从app/controllers加载文件,只通过查询他们的名称就可以了,就像Rails 一样。然后你需要新控制器。

现在,进入rulers的目录,

$ git add .
$ gem build reulers.gemspec
$ gem install rulers-0.0.2.gem

然后在best_quotes目录下,输入“rackup -p 3001”。最后,打开你的 浏览器,在地址栏输入“http://localhost:3001/quotes/a_quote”查看效果。

假如你之前没出错,你应该看到哈姆莱特的名言。你也正看见你的第一个控制器的第一个动 作。

假如你没有看到,请确定你在URL地址里包含了“quotes/a_quote”,就像你上面看见的一样。

几乎搞定了!

现在,看看你运行rackup的控制台。看看屏幕。看见那个错误了吗?在有些浏览器上可能看 不到,但是你也可能有像这样一个错误:

NameError: wrong constant name Favicon.icoController .../gems/rulers-0.0.3/lib/rulers/routing.rb:9:inconst_get' .../gems/rulers-0.0.3/lib/rulers/routing.rb:9:inget_controller_and_action' .../gems/rulers-0.0.3/lib/rulers.rb:7:in call' .../gems/rack-1.4.1/lib/rack/lint.rb:48:in   _call' (...more lines...) 127.0.0.1 - - [21/Feb/2012 19:46:51] "GET / favicon.ico HTTP/1.1" 500 42221 0.0092

你正看见一个浏览器请求一个文件失败的错误信息...嗯...看看最后一行...哦, favicon.ico。大部分的浏览器会自动请求这个文件。最终我们会让我们的框架或者网页服 务器负责服务像这样的静态文件。但是现在,我们将只是很糟糕的实现一下。

打开rulers/lib/rulers.rb,看看Rulers::Application#call。我们能明显地检查一下 PATH_INFO是不是/favicon.ico,如果是的话就返回404:

# rulers/lib/rulers.rb
module Rulers
  class Application
    def call(env)
      if env['PATH_INFO'] == '/favicon.ico'
        return [404, { 'Content-Type' => 'text/html' }, []]
      end
      klass, act = get_controller_and_action(env)
      controller = klass.new(env)
      text = controller.senc(act)
      [200, {'Content-Type' => 'text/html'},
        [text]]
      end
    end
  end
end

非常糟糕的hack?肯定了。不过最后我们会解决它的。现在,我们就能看见真正的错误,没 有了这种浏览器特性引起的错误了。

复习

你刚刚建立了很基础的路由,还有你可以路由到控制器的动作。假如添加过多控制器动作, 你可以得到更多路由。Rulers 0.0.2只是刚刚后建立一个超级简单的网站。后面我们将添加 更多特性。

你已经学习了一点Rack -- 在这章的“Rails”一节还会看到一些。你已经通过LOAD_PATH和 const_get见识了Rails的一点魔法,我们在后面也还会看到它们。

练习

练习1. 调试Rack环境

打开app/controllers/quotes_controller.rb,修改为下面这样:

# best_quotes/app/controllers/quotes_controller.rb
class QuotesController < Rulers::Controller
  def a_quote
    "There is nothing either good or bad" +
    "but thinking makes it so."         +
    "\n<pre>\n#{env}\n</pre>"
  end
end

现在,重启服务器 -- 假如你只是修改了应用程序(best_quotes),你不需要重建gem. 刷新浏览器,你应该看见一个大的hash表,充满了好多有趣的信息。它只有一行,所以为了 更清晰,我为你重新把它格式化了:

{"GATEWAY_INTERFACE"=>"CGI/1.1", "PATH_INFO"=>"/ quotes/a_quote", "QUERY_STRING"=>"", "REMOTE_ADDR"=>"127.0.0.1", "REMOTE_HOST"=>"localhost", "REQUEST_METHOD"=>"GET", "REQUEST_URI"=>"http://localhost:3001/quotes/a_quote", "SCRIPT_NAME"=>"", "SERVER_NAME"=>"localhost", "SERVER_PORT"=>"3001", "SERVER_PROTOCOL"=>"HTTP/1.1", "SERVER_SOFTWARE"=>"WEBrick/1.3.1 (Ruby/1.9.3/2012-11-10)", "HTTP_HOST"=>"localhost:3001", "HTTP_CONNECTION"=>"keep-alive", "HTTP_CACHE_CONTROL"=>"max-age=0", "HTTP_USER_AGENT"=>"Mozilla/5.0 (Macintosh; Intel Mac OS X 10_6_8) AppleWebKit/537.11 (KHTML, like Gecko) Chrome/23.0.1271.64 Safari/537.11", "HTTP_ACCEPT"=>"text/html,application/xhtml+xml,application/xml;q=0.9,/;q=0.8", "HTTP_ACCEPT_ENCODING"=>"gzip,deflate,sdch", "HTTP_ACCEPT_LANGUAGE"=>"en-US,en;q=0.8", "HTTP_ACCEPT_CHARSET"=>"ISO-8859-1,utf-8;q=0.7,*;q=0.3", "rack.version"=>[1, 1], "rack.input"=>#>, "rack.errors"=>#>>, "rack.multithread"=>true, "rack.multiprocess"=>false, "rack.run_once"=>false, "rack.url_scheme"=>"http", "HTTP_VERSION"=>"HTTP/1.1", "REQUEST_PATH"=>"/quotes/a_quote"}

看起来内容很多,对不对?你的程序从Rack返回的所有信息都有了。何时你的Rails控制器使用 像“post?”这样的动作?,通过查看Rack的环境变量揭开它的面纱。假如你的控制器检查是否 env['REQUEST_METHOD'] == 'POST',你可以很容易的添加你自己的“post?”方法?

现在好了,你现在能看见Rails必须用到的每个内容。Rails需要知道的的进来的请求中的每 一项都是从这个hash中抽取出来的。

练习2. 调试异常

让我们给我们的控制器中添加一个新方法,让他抛出异常:

# best_quotes/app/controllers/quotes_controller.rb
class QuotesController < Rulers::Controller
  def exception
    raise 'It's a bad one!'
  end
end

重新运行rackup,然后在浏览器里访问“http://localhost:3001/quotes/exception”,它应 该抛出一个一场。你可能看到美化了的页面,说在quotes/exception有一个RuntimeError和 大的堆栈跟踪信息。

在第八章,我们将深入Rack中间件,你看到那些的内容的原因。它不是内建在你的浏览器的, 你可以通过设置RACK_ENV为production(生产环境)来关闭它。一般来说,它是开发环境才 有的调试工具,你正受益于此。

然而,用它不是必须的。你可以在你的Rulers应用程序里添加begin/rescue/end块来处理例 外。

去rulers/lib/rulers.rb,然后在你的call方法里,把controller.send()函数添加进begin /rescue/end块里。现在你必须决定怎么处理抛出的异常 -- 你可以从一个简单的错误页面开始,或者自定义500页面,或者其他你喜欢的。

你的框架还可以对错误作出那些处理呢?

练习3. 根路径和路由

你不能直接访问“http://localhost:3001”看程序工作正不正常, 这点很不方便。只是得到 异常不能告诉你是否程序出错。

回到rulers/lib/rulers.rb。在检查favicon.ico的代码下面,你可以再添加一个检查,看 看PATH_INFO是不是“/”。

首先,试着硬编码,让它总是重定向到“/quotes/a_quote”然后再你的浏览器里测试。然后, 试试下面的一个:

  • 返回已知文件的内容 -- 比如public/index.html?
  • 查找HomeController和index动作
  • 额外得分:试试浏览器重定向。这个请求返回200或404以外的代码,然后设置header。

在第9章,我们将建立更多配置的路由,更像真正的Rails。直到那时,在你的框架里用Ruby hack你的路由就非常容易了。

在Rails里

在Rails里ActionPack包含控制器。Rails也有ApplicationController,它继承了控制器基 类,然后每个控制器继承它。你的框架也可以!

不同的Rails版本的路由实现有实质上的不同。你可以通过以下网址了解一下现在的 “http://guides.rubyonrails.org/routing.html”,“Rails路由从外到内”。Rulers现在的 路由和旧版本Rails1.2的路由(也是Rails2.3默认的方式)相似。它会自动查找控制器和动 作,不需要指定单个的路由。虽然不太安全,但是对于开始来说并不太差。Rails 3默认的 路由通过生成许多单个的匹配的路由来实现,所以和这个不是很一样。

Rails把Rack信息打包到“request”对象,而不是只把hash包含到相应的控制器。当你想要抽 象一点的时候,这个是个好主意--为某个特殊的变量一般化值,例如,或者读和设置cookie 来储存session数据。Rails做了许多工作,在第6章,你会看见Rulers怎么实现它。Rails在 底层也使用Rack,所以用到的基础信息和你是一样的。

Rack是简单的、CGI一样的接口,比你想象的功能要少。假如你好奇,看看“http:// rack.rubyforge.org/doc/SPEC.html”,包含了所有细节。有点晦涩,但是它可以告诉你你 需要知道的所有。

随着后面的学习,你将会了解更多关于Rack的知识。但是对于没有耐心的读者,Rails包含了 一篇关于怎样使用Rack的指南“http://guides.rubyonrails.org/rails_on_rack.html”。它 里面满满都是你可以应用在自己的框架里的内容。有些在本书的后面内容里也会添加。

相关文章
|
4月前
|
设计模式 前端开发 数据库
深入理解MVC设计模式:构建高效Web应用程序的基石
【7月更文挑战第4天】在软件工程领域,设计模式是解决常见问题的一系列经过验证的方法。其中,Model-View-Controller(MVC)设计模式自诞生以来,便成为了构建用户界面,特别是Web应用程序的黄金标准。MVC通过将应用程序逻辑分离为三个核心组件,提高了代码的可维护性、可扩展性和重用性。本文将深入探讨MVC设计模式的原理,并通过一个简单的代码示例展示其应用。
157 0
|
6月前
|
前端开发 Java 测试技术
Java一分钟之Spring MVC:构建Web应用
【5月更文挑战第15天】Spring MVC是Spring框架的Web应用模块,基于MVC模式实现业务、数据和UI解耦。常见问题包括:配置DispatcherServlet、Controller映射错误、视图解析未设置、Model数据传递遗漏、异常处理未配置、依赖注入缺失和忽视单元测试。解决这些问题可提升代码质量和应用性能。注意配置`web.xml`、`@RequestMapping`、`ViewResolver`、`Model`、`@ExceptionHandler`、`@Autowired`,并编写测试用例。
339 3
|
1月前
|
前端开发 Java
【案例+源码】详解MVC框架模式及其应用
【案例+源码】详解MVC框架模式及其应用
59 0
|
2月前
|
前端开发 安全 Java
技术进阶:使用Spring MVC构建适应未来的响应式Web应用
【9月更文挑战第2天】随着移动设备的普及,响应式设计至关重要。Spring MVC作为强大的Java Web框架,助力开发者创建适应多屏的应用。本文推荐使用Thymeleaf整合视图,通过简洁的HTML代码提高前端灵活性;采用`@ResponseBody`与`Callable`实现异步处理,优化应用响应速度;运用`@ControllerAdvice`统一异常管理,保持代码整洁;借助Jackson简化JSON处理;利用Spring Security增强安全性;并强调测试的重要性。遵循这些实践,将大幅提升开发效率和应用质量。
64 7
|
3月前
|
设计模式 存储 前端开发
MVC 框架的主要问题是什么?
【8月更文挑战第30天】
74 0
|
5月前
|
安全 前端开发 测试技术
安全开发-PHP应用&模版引用&Smarty渲染&MVC模型&数据联动&RCE安全&TP框架&路由访问&对象操作&内置过滤绕过&核心漏洞
安全开发-PHP应用&模版引用&Smarty渲染&MVC模型&数据联动&RCE安全&TP框架&路由访问&对象操作&内置过滤绕过&核心漏洞
|
6月前
|
前端开发 JavaScript 开发者
深入理解MVC和MVVM:构建现代Web应用的利器
深入理解MVC和MVVM:构建现代Web应用的利器
|
6月前
|
前端开发 Java Spring
Java Web ——MVC基础框架讲解及代码演示(下)
Java Web ——MVC基础框架讲解及代码演示
65 1
|
6月前
|
前端开发 Java 应用服务中间件
Spring MVC框架概述
Spring MVC 是一个基于Java的轻量级Web框架,采用MVC设计模型实现请求驱动的松耦合应用开发。框架包括DispatcherServlet、HandlerMapping、Handler、HandlerAdapter、ViewResolver核心组件。DispatcherServlet协调这些组件处理HTTP请求和响应,Controller处理业务逻辑,Model封装数据,View负责渲染。通过注解@Controller、@RequestMapping等简化开发,支持RESTful请求。Spring MVC具有清晰的角色分配、Spring框架集成、多种视图技术支持以及异常处理等优点。
83 1
|
6月前
|
设计模式 前端开发 网络协议
Java Web ——MVC基础框架讲解及代码演示(上)
Java Web ——MVC基础框架讲解及代码演示
51 0